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