#!/usr/bin/python import argparse import math def mean(v): return sum(v) / len(v) def stddev(v): m = mean(v) d = [(n - m) ** 2 for n in v] return math.sqrt(mean(d)) def median(v): s = sorted(v) l = len(s) m = l / 2 if l % 2: return s[m] else: return mean([s[m - 1], s[m]]) def min(v): return sorted(v)[0] def max(v): return sorted(v)[-1] class Dataset(object): def __init__(self, runs): self.runs = runs def __repr__(self): return repr(self.runs) def merge(self): runs = [[]] for run in self.runs: runs[0] += run return Dataset(runs) def unary(self, op): runs = [] for run in self.runs: runs.append(op(run)) return Dataset(runs) def binary(self, y, op): runs = [] for runx, runy in zip(self.runs, y.runs): runs.append(op(runx, runy)) return Dataset(runs) def binary_values(self, y, op): return self.binary(y, lambda rx, ry: [op(x, y) for x, y in zip(rx, ry)]) def binary_constant(self, c, op): runs = [] for run in self.runs: r = [] for value in run: r.append(op(value, c)) runs.append(r) return Dataset(runs) def __add__(self, y): if type(y) == Dataset: return self.binary_values(y, float.__add__) return self.binary_constant(y, float.__add__) def __sub__(self, y): if type(y) == Dataset: return self.binary_values(y, float.__sub__) return self.binary_constant(y, float.__sub__) def __mul__(self, y): if type(y) == Dataset: return self.binary_values(y, float.__mul__) return self.binary_constant(y, float.__mul__) def __truediv__(self, y): if type(y) == Dataset: return self.binary_values(y, float.__truediv__) return self.binary_constant(y, float.__truediv__) def builtin_read(filename): """Read a space-separated table of runs into a dictionary. A00 B00 A01 B01 A10 B10 A11 B11 { 1: [[A00, A01], [A10, A11]], 2: [[B00, B01], [B10, B11]] } """ data = {} run = {} for line in open(filename): if len(line.strip()) == 0: for key in run: if not key in data: data[key] = Dataset([]) data[key].runs.append(run[key]) run = {} continue for key, value in enumerate(line.split()): value = float(value) if not key in run: run[key] = [] run[key].append(value) for key in run: if not key in data: data[key] = Dataset([]) data[key].runs.append(run[key]) return data def builtin_readdict(filename): """Read a dictionary style file into a dictionary. A A00 B B00 A A01 B B01 A A10 B B10 A A11 B B11 { A: [[A00, A01], [A10, A11]], B: [[B00, B01], [B10, B11]] } """ data = {} run = {} for line in open(filename): if len(line.strip()) == 0: for key in run: if not key in data: data[key] = Dataset([]) data[key].runs.append(run[key]) run = {} continue key, value = line.split() value = float(value) if not key in run: run[key] = [] run[key].append(value) for key in run: if not key in data: data[key] = Dataset([]) data[key].runs.append(run[key]) return data def builtin_merge(s): """Merge all runs into a single one. [[v0, ...], ...] -> [[v0, ...]] """ return s.merge() def builtin_fold(s): """Fold each run into the delta between its first and last value. [[v0, ...], ...] -> [[vD], ...] """ return s.unary(lambda r: [r[-1] - r[0]]) def builtin_mean(s): """Fold each run into the arithmetic mean of its values. [[v0, ...], ...] -> [[vmean], ...] """ return s.unary(lambda r: [mean(r)]) def builtin_stddev(s): """Fold each run into the standard deviation of its values. [[v0, ...], ...] -> [[vstddev], ...] """ return s.unary(lambda r: [stddev(r)]) def builtin_min(s): return s.unary(lambda r: [min(r)]) def builtin_max(s): return s.unary(lambda r: [max(r)]) def builtin_median(s): return s.unary(lambda r: [median(r)]) senv = { 'read': builtin_read, 'readdict': builtin_readdict, 'merge': builtin_merge, 'fold': builtin_fold, 'mean': builtin_mean, 'stddev': builtin_stddev, 'min': builtin_min, 'max': builtin_max, 'median': builtin_median } parser = argparse.ArgumentParser() parser.add_argument('-s', '--spec', action='append', default=[]) parser.add_argument('name', nargs='+') args = parser.parse_args() datas = [] items = [] def save_section(type_, name, body): if type_ == 'data': datas.append((name, body)) elif type_ == 'item': items.append((name, body)) else: print('WARNING: unknown section type "%s"' % type_) for spec in args.spec: type_ = None name = None body = '' for line in open(spec): line = line.strip() if len(line) == 0 or line.startswith('#'): continue if line.startswith('%'): if type_: save_section(type_, name, body) parts = line.split(None, 1) type_ = parts[0][1:] name = parts[1] body = '' elif type_: body += line if type_: save_section(type_, name, body) values = {} for name in args.name: env = { 'name': name } env.update(senv) for data in datas: try: env[data[0]] = eval(data[1], env) except Exception as e: print('ERROR in data expression "%s"' % data[1]) raise e values[name] = {} for item in items: try: data = eval(item[1], env) except Exception as e: print('ERROR in item expression "%s"' % item[1]) raise e if len(data.runs) != 1: raise ValueError('more than one run for item "%s": %s' % (item[0], data.runs)) if len(data.runs[0]) != 1: raise ValueError('more than one value in run 0 for item "%s": %s' % (item[0], data.runs)) values[name][item[0]] = data.runs[0][0] report = [['']] widths = [0] for name in args.name: report[0].append(name) widths.append(len(name)) for item in items: row = [item[0]] if len(item[0]) > widths[0]: widths[0] = len(item[0]) for i, name in enumerate(args.name, start=1): old = float(values[args.name[0]][item[0]]) new = float(values[name][item[0]]) delta = (new - old) / (old + 1) * 100 cell = '%.2f (%+9.2f%%)' % (values[name][item[0]], delta) row.append(cell) if len(cell) > widths[i]: widths[i] = len(cell) report.append(row) for row in report: for i, col in enumerate(row): fmt = '%*s' if not i: fmt = '%-*s' print(fmt % (widths[i], col), end='\t') print('')