info.py revision 2015
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.bins, 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', 'bins', '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__['bins'] = None 763 stat.__dict__['ticks'] = None 764 stat.__dict__.update(data.__dict__) 765 766 return stat 767 768