info.py revision 1986:b7d11bab72eb
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        return self.op(val0, val1)
258
259    def __vectorvalue__(self, run, index):
260        if scalar(self.arg0):
261            val0 = value(self.arg0, run)
262        if vector(self.arg0):
263            val0 = value(self.arg0, run, index)
264        if scalar(self.arg1):
265            val1 = value(self.arg1, run)
266        if vector(self.arg1):
267            val1 = value(self.arg1, run, index)
268
269        if val0 is None or val1 is None:
270            return None
271
272        return self.op(val0, val1)
273
274    def __vectorlen__(self):
275        if vector(self.arg0) and scalar(self.arg1):
276            return len(self.arg0)
277        if scalar(self.arg0) and vector(self.arg1):
278            return len(self.arg1)
279
280        len0 = len(self.arg0)
281        len1 = len(self.arg1)
282
283        if len0 != len1:
284            raise AttributeError, \
285                  "vectors of different lengths %d != %d" % (len0, len1)
286
287        return len0
288
289    def __str__(self):
290        ops = { operator.__add__ : '+',
291                operator.__sub__ : '-',
292                operator.__mul__ : '*',
293                operator.__div__ : '/',
294                operator.__truediv__ : '/',
295                operator.__floordiv__ : '//' }
296
297        return '(%s %s %s)' % (str(self.arg0), ops[self.op], str(self.arg1))
298
299class Proxy(Value):
300    def __init__(self, name, dict):
301        self.name = name
302        self.dict = dict
303
304    def __unproxy__(self):
305        return unproxy(self.dict[self.name])
306
307    def __getitem__(self, index):
308        return ItemProxy(self, index)
309
310    def __getattr__(self, attr):
311        return AttrProxy(self, attr)
312
313    def __str__(self):
314        return str(self.dict[self.name])
315
316class ItemProxy(Proxy):
317    def __init__(self, proxy, index):
318        self.proxy = proxy
319        self.index = index
320
321    def __unproxy__(self):
322        return unproxy(unproxy(self.proxy)[self.index])
323
324    def __str__(self):
325        return '%s[%s]' % (self.proxy, self.index)
326
327class AttrProxy(Proxy):
328    def __init__(self, proxy, attr):
329        self.proxy = proxy
330        self.attr = attr
331
332    def __unproxy__(self):
333        return unproxy(getattr(unproxy(self.proxy), self.attr))
334
335    def __str__(self):
336        return '%s.%s' % (self.proxy, self.attr)
337
338class ProxyGroup(object):
339    def __init__(self, dict=None, **kwargs):
340        self.__dict__['dict'] = {}
341
342        if dict is not None:
343            self.dict.update(dict)
344
345        if kwargs:
346            self.dict.update(kwargs)
347
348    def __getattr__(self, name):
349        return Proxy(name, self.dict)
350
351    def __setattr__(self, attr, value):
352        self.dict[attr] = value
353
354class ScalarStat(Statistic,Scalar):
355    def __value__(self, run):
356        if run not in self.data:
357            return None
358        return self.data[run][0][0]
359
360    def display(self, run=None):
361        import display
362        p = display.Print()
363        p.name = self.name
364        p.desc = self.desc
365        p.value = value(self, run)
366        p.flags = self.flags
367        p.precision = self.precision
368        if display.all or (self.flags & flags.printable):
369            p.display()
370
371class VectorStat(Statistic,Vector):
372    def __value__(self, run, item):
373        if run not in self.data:
374            return None
375        return self.data[run][item][0]
376
377    def __len__(self):
378        return self.x
379
380    def display(self, run=None):
381        import display
382        d = display.VectorDisplay()
383        d.name = self.name
384        d.desc = self.desc
385        d.value = [ value(self, run, i) for i in xrange(len(self)) ]
386        d.flags = self.flags
387        d.precision = self.precision
388        d.display()
389
390class Formula(Value):
391    def __getattribute__(self, attr):
392        if attr not in ( '__scalar__', '__vector__', '__value__', '__len__' ):
393            return super(Formula, self).__getattribute__(attr)
394
395        formula = re.sub(':', '__', self.formula)
396        value = eval(formula, self.source.stattop)
397        return getattr(value, attr)
398
399    def __str__(self):
400        return self.name
401
402class SimpleDist(Statistic):
403    def __init__(self, sums, squares, samples):
404        self.sums = sums
405        self.squares = squares
406        self.samples = samples
407
408    def display(self, name, desc, flags, precision):
409        import display
410        p = display.Print()
411        p.flags = flags
412        p.precision = precision
413
414        if self.samples > 0:
415            p.name = name + ".mean"
416            p.value = self.sums / self.samples
417            p.display()
418
419            p.name = name + ".stdev"
420            if self.samples > 1:
421                var = (self.samples * self.squares - self.sums ** 2) \
422                      / (self.samples * (self.samples - 1))
423                if var >= 0:
424                    p.value = math.sqrt(var)
425                else:
426                    p.value = 'NaN'
427            else:
428                p.value = 0.0
429            p.display()
430
431        p.name = name + ".samples"
432        p.value = self.samples
433        p.display()
434
435    def comparable(self, other):
436        return True
437
438    def __eq__(self, other):
439        return self.sums == other.sums and self.squares == other.squares and \
440               self.samples == other.samples
441
442    def __isub__(self, other):
443        self.sums -= other.sums
444        self.squares -= other.squares
445        self.samples -= other.samples
446        return self
447
448    def __iadd__(self, other):
449        self.sums += other.sums
450        self.squares += other.squares
451        self.samples += other.samples
452        return self
453
454    def __itruediv__(self, other):
455        if not other:
456            return self
457        self.sums /= other
458        self.squares /= other
459        self.samples /= other
460        return self
461
462class FullDist(SimpleDist):
463    def __init__(self, sums, squares, samples, minval, maxval,
464                 under, vec, over, min, max, bsize, size):
465        self.sums = sums
466        self.squares = squares
467        self.samples = samples
468        self.minval = minval
469        self.maxval = maxval
470        self.under = under
471        self.vec = vec
472        self.over = over
473        self.min = min
474        self.max = max
475        self.bsize = bsize
476        self.size = size
477
478    def display(self, name, desc, flags, precision):
479        import display
480        p = display.Print()
481        p.flags = flags
482        p.precision = precision
483
484        p.name = name + '.min_val'
485        p.value = self.minval
486        p.display()
487
488        p.name = name + '.max_val'
489        p.value = self.maxval
490        p.display()
491
492        p.name = name + '.underflow'
493        p.value = self.under
494        p.display()
495
496        i = self.min
497        for val in self.vec[:-1]:
498            p.name = name + '[%d:%d]' % (i, i + self.bsize - 1)
499            p.value = val
500            p.display()
501            i += self.bsize
502
503        p.name = name + '[%d:%d]' % (i, self.max)
504        p.value = self.vec[-1]
505        p.display()
506
507
508        p.name = name + '.overflow'
509        p.value = self.over
510        p.display()
511
512        SimpleDist.display(self, name, desc, flags, precision)
513
514    def comparable(self, other):
515        return self.min == other.min and self.max == other.max and \
516               self.bsize == other.bsize and self.size == other.size
517
518    def __eq__(self, other):
519        return self.sums == other.sums and self.squares == other.squares and \
520               self.samples == other.samples
521
522    def __isub__(self, other):
523        self.sums -= other.sums
524        self.squares -= other.squares
525        self.samples -= other.samples
526
527        if other.samples:
528            self.minval = min(self.minval, other.minval)
529            self.maxval = max(self.maxval, other.maxval)
530            self.under -= under
531            self.vec = map(lambda x,y: x - y, self.vec, other.vec)
532            self.over -= over
533        return self
534
535    def __iadd__(self, other):
536        if not self.samples and other.samples:
537            self = other
538            return self
539
540        self.sums += other.sums
541        self.squares += other.squares
542        self.samples += other.samples
543
544        if other.samples:
545            self.minval = min(self.minval, other.minval)
546            self.maxval = max(self.maxval, other.maxval)
547            self.under += other.under
548            self.vec = map(lambda x,y: x + y, self.vec, other.vec)
549            self.over += other.over
550        return self
551
552    def __itruediv__(self, other):
553        if not other:
554            return self
555        self.sums /= other
556        self.squares /= other
557        self.samples /= other
558
559        if self.samples:
560            self.under /= other
561            for i in xrange(len(self.vec)):
562                self.vec[i] /= other
563            self.over /= other
564        return self
565
566class Dist(Statistic):
567    def display(self):
568        import display
569        if not display.all and not (self.flags & flags.printable):
570            return
571
572        self.dist.display(self.name, self.desc, self.flags, self.precision)
573
574    def comparable(self, other):
575        return self.name == other.name and \
576               self.dist.compareable(other.dist)
577
578    def __eq__(self, other):
579        return self.dist == other.dist
580
581    def __isub__(self, other):
582        self.dist -= other.dist
583        return self
584
585    def __iadd__(self, other):
586        self.dist += other.dist
587        return self
588
589    def __itruediv__(self, other):
590        if not other:
591            return self
592        self.dist /= other
593        return self
594
595class VectorDist(Statistic):
596    def display(self):
597        import display
598        if not display.all and not (self.flags & flags.printable):
599            return
600
601        if isinstance(self.dist, SimpleDist):
602            return
603
604        for dist,sn,sd,i in map(None, self.dist, self.subnames, self.subdescs,
605                                range(len(self.dist))):
606            if len(sn) > 0:
607                name = '%s.%s' % (self.name, sn)
608            else:
609                name = '%s[%d]' % (self.name, i)
610
611            if len(sd) > 0:
612                desc = sd
613            else:
614                desc = self.desc
615
616            dist.display(name, desc, self.flags, self.precision)
617
618        if (self.flags & flags.total) or 1:
619            if isinstance(self.dist[0], SimpleDist):
620                disttotal = SimpleDist( \
621                    reduce(sums, [d.sums for d in self.dist]),
622                    reduce(sums, [d.squares for d in self.dist]),
623                    reduce(sums, [d.samples for d in self.dist]))
624            else:
625                disttotal = FullDist( \
626                    reduce(sums, [d.sums for d in self.dist]),
627                    reduce(sums, [d.squares for d in self.dist]),
628                    reduce(sums, [d.samples for d in self.dist]),
629                    min([d.minval for d in self.dist]),
630                    max([d.maxval for d in self.dist]),
631                    reduce(sums, [d.under for d in self.dist]),
632                    reduce(sums, [d.vec for d in self.dist]),
633                    reduce(sums, [d.over for d in self.dist]),
634                    dist[0].min,
635                    dist[0].max,
636                    dist[0].bsize,
637                    dist[0].size)
638
639            name = '%s.total' % (self.name)
640            desc = self.desc
641            disttotal.display(name, desc, self.flags, self.precision)
642
643    def comparable(self, other):
644        return self.name == other.name and \
645               alltrue(map(lambda x, y : x.comparable(y),
646                           self.dist,
647                           other.dist))
648
649    def __eq__(self, other):
650        return alltrue(map(lambda x, y : x == y, self.dist, other.dist))
651
652    def __isub__(self, other):
653        if isinstance(self.dist, (list, tuple)) and \
654               isinstance(other.dist, (list, tuple)):
655            for sd,od in zip(self.dist, other.dist):
656                sd -= od
657        else:
658            self.dist -= other.dist
659        return self
660
661    def __iadd__(self, other):
662        if isinstance(self.dist, (list, tuple)) and \
663               isinstance(other.dist, (list, tuple)):
664            for sd,od in zip(self.dist, other.dist):
665                sd += od
666        else:
667            self.dist += other.dist
668        return self
669
670    def __itruediv__(self, other):
671        if not other:
672            return self
673        if isinstance(self.dist, (list, tuple)):
674            for dist in self.dist:
675                dist /= other
676        else:
677            self.dist /= other
678        return self
679
680class Vector2d(Statistic):
681    def display(self):
682        import display
683        if not display.all and not (self.flags & flags.printable):
684            return
685
686        d = display.VectorDisplay()
687        d.__dict__.update(self.__dict__)
688
689        if self.__dict__.has_key('ysubnames'):
690            ysubnames = list(self.ysubnames)
691            slack = self.x - len(ysubnames)
692            if slack > 0:
693                ysubnames.extend(['']*slack)
694        else:
695            ysubnames = range(self.x)
696
697        for x,sname in enumerate(ysubnames):
698            o = x * self.y
699            d.value = self.value[o:o+self.y]
700            d.name = '%s[%s]' % (self.name, sname)
701            d.display()
702
703        if self.flags & flags.total:
704            d.value = []
705            for y in range(self.y):
706                xtot = 0.0
707                for x in range(self.x):
708                    xtot += self.value[y + x * self.x]
709                d.value.append(xtot)
710
711            d.name = self.name + '.total'
712            d.display()
713
714    def comparable(self, other):
715        return self.name == other.name and self.x == other.x and \
716               self.y == other.y
717
718    def __eq__(self, other):
719        return True
720
721    def __isub__(self, other):
722        return self
723
724    def __iadd__(self, other):
725        return self
726
727    def __itruediv__(self, other):
728        if not other:
729            return self
730        return self
731
732def NewStat(source, data):
733    stat = None
734    if data.type == 'SCALAR':
735        stat = ScalarStat()
736    elif data.type == 'VECTOR':
737        stat = VectorStat()
738    elif data.type == 'DIST':
739        stat = Dist()
740    elif data.type == 'VECTORDIST':
741        stat = VectorDist()
742    elif data.type == 'VECTOR2D':
743        stat = Vector2d()
744    elif data.type == 'FORMULA':
745        stat = Formula()
746
747    stat.__dict__['source'] = source
748    stat.__dict__['bins'] = None
749    stat.__dict__['ticks'] = None
750    stat.__dict__.update(data.__dict__)
751
752    return stat
753
754