def __init__(self, fname, out_dir=constants.OUTPUT_DIR):
     # Init data
     if fname.endswith(".rktd"):
         # Parse the .rktd file into a .tab file
         self.source = self.of_rktd(fname)
         self.of_tab(self.source)
     elif fname.endswith(".tab"):
         self.source = fname
         self.of_tab(fname)
     else:
         raise NotImplementedError("Tabfile cannot parse '%s'" % fname)
     # Set project name
     self.project_name = fname.rsplit(".", 1)[0].rsplit("/")[-1]
     # Try to set module graph
     self.graph = ModuleGraph(fname)
     self.module_names = self.graph.get_module_names()
     self.output_dir = out_dir
class TabfileSummary(AbstractSummary):
    def __init__(self, fname, out_dir=constants.OUTPUT_DIR):
        # Init data
        if fname.endswith(".rktd"):
            # Parse the .rktd file into a .tab file
            self.source = self.of_rktd(fname)
            self.of_tab(self.source)
        elif fname.endswith(".tab"):
            self.source = fname
            self.of_tab(fname)
        else:
            raise NotImplementedError("Tabfile cannot parse '%s'" % fname)
        # Set project name
        self.project_name = fname.rsplit(".", 1)[0].rsplit("/")[-1]
        # Try to set module graph
        self.graph = ModuleGraph(fname)
        self.module_names = self.graph.get_module_names()
        self.output_dir = out_dir

    def results_of_config(self, config):
        return util.fold_file(
            self.source,
            None,
            lambda acc, row: acc or (util.stats_of_row([int(x) for x in row[1:]]) if row[0] == config else None),
        )

    def render(self, output_port):
        title = "Ground Truth Results: %s" % self.project_name
        self.render_title(output_port, title)
        self.render_summary(output_port)
        best_cfgs = self.best_rows(
            config.is_gradual, lambda x, y: self.stats_by_config[x]["mean"] > self.stats_by_config[y]["mean"]
        )
        worst_cfgs = self.best_rows(
            config.is_gradual, lambda x, y: self.stats_by_config[x]["mean"] < self.stats_by_config[y]["mean"]
        )
        self.render_overall(
            output_port,
            ("untyped", config.is_untyped),
            ("gradual", config.is_gradual),
            ("fastest(%s)" % best_cfgs[0], lambda x: x == best_cfgs[0]),
            ("slowest(%s)" % worst_cfgs[0], lambda x: x == worst_cfgs[0]),
            ("typed", config.is_typed),
        )
        print(
            "Num. within 2x: %s"
            % len(
                self.stats_of_predicate(
                    lambda x: self.stats_by_config[x]["mean"]
                    < 2 * self.stats_by_config["0" * self.get_num_modules()]["mean"]
                )
            ),
            file=output_port,
        )
        print(latex.subsection("Aggregate Figures"), file=output_port)
        self.render_normalized(
            output_port,
            ("untyped", config.is_untyped),
            ("gradual", config.is_gradual),
            ("top %s" % len(best_cfgs), lambda x: x in best_cfgs),
            ("bottom %s" % len(worst_cfgs), lambda x: x in worst_cfgs),
            ("typed", config.is_typed),
        )
        self.render_absolute(
            output_port, *[(str(n), config.has_typed_modules(n)) for n in range(self.get_num_modules())]
        )
        baseline = self.stats_of_config("0" * self.get_num_modules())["mean"]
        self.render_graphs(
            output_port, worst_cfgs, baseline, title="Top %s slowest gradually-typed configurations" % len(worst_cfgs)
        )
        self.render_graphs(
            output_port, best_cfgs, baseline, title="Top %s fastest gradually-typed configurations" % len(best_cfgs)
        )
        # self.render_all_paths(output_port, [1,2,3,4])
        # self.render_cutoff_paths(output_port)
        print(latex.end(), file=output_port)

    ### rendering
    def render_summary(self, output_port):
        AbstractSummary.render_summary(self, output_port)
        print("Total of %s configurations" % self.get_num_configurations(), file=output_port)
        print("Ran each configuration %s times" % self.num_iters, file=output_port)

    def render_graphs(self, output_port, cfgs, baseline, title="Module Graphs"):
        print(latex.subsection(title), file=output_port)
        for cfg in cfgs:
            mean = self.stats_of_config(cfg)["mean"]
            diff, txt = latex.difference(mean, baseline)
            g = self.graph_config(
                cfg,
                title="Config %s: %s %s than baseline" % (cfg, diff, txt),
                output="%s-graph-%s.png" % (self.project_name, cfg),
            )
            print(latex.figure(g), file=output_port)

    def render_all_paths(self, output_port, transitivity=[1]):
        """
            Options:
            - transitivity : How many transitive edges to include.
                             By default, edges are only between consecutive levels.
                             If argument is a list, analyzes one graph of each transitivity
        """
        print(latex.subsection("Lattices+Freedom"), file=output_port)
        typed_mean = self.stats_of_config("1" * self.get_num_modules())["mean"]
        untyped_mean = self.stats_of_config("0" * self.get_num_modules())["mean"]
        rows = []
        for trans in transitivity:
            print("Building lattice for %s with transitivity %s" % (self.project_name, trans))
            lattice = self.make_lattice(transitivity=trans)
            untyped_config = "0" * self.get_num_modules()
            typed_config = "1" * self.get_num_modules()
            paths = networkx.all_simple_paths(lattice, source=untyped_config, target=typed_config)
            weights = [self.max_weight(lattice, path) for path in paths]
            num_release_u = sum((1 for x in weights if x < (untyped_mean * constants.DELIVERABLE)))
            num_dev_u = sum((1 for x in weights if x < (untyped_mean * constants.USABLE)))
            num_x_u = sum((1 for x in weights if x < (untyped_mean * 15)))
            num_release_t = sum((1 for x in weights if x < (typed_mean * constants.DELIVERABLE)))
            num_dev_t = sum((1 for x in weights if x < (typed_mean * constants.USABLE)))
            num_x_t = sum((1 for x in weights if x < (typed_mean * 15)))
            num_paths = len(weights)
            rows.append(
                [str(trans), str(num_paths)]
                + [
                    "%s (%s\\%%)" % (x, round((x / num_paths) * 100, 2))
                    for x in [num_release_u, num_dev_u, num_x_u, num_release_t, num_dev_t, num_x_t]
                ]
            )
        print(
            latex.table(
                [
                    "Fuel",
                    "Total",
                    "$<$ 2x untyped",
                    "$<$ 4x untyped",
                    "$<$ 15x untyped",
                    "$<$ 2x typed",
                    "$<$ 4x typed",
                    "$<$ 15x typed",
                ],
                rows,
            ),
            file=output_port,
        )

    def render_cutoff_paths(self, output_port, xmax=None, ymax=None):
        print(latex.subsection("Paths with cutoff"), file=output_port)
        # Build a lattice for each cluster size {1 .. num_modules-1}
        print("Building lattice for %s" % self.project_name)
        lattice = self.make_lattice(transitivity=self.get_num_modules())
        # xmax = max((e[2]["weight"] for e in lattice.edges_iter(data=True)))
        untyped_config = "0" * self.get_num_modules()
        untyped_mean = self.stats_of_config(untyped_config)["mean"]
        typed_config = "1" * self.get_num_modules()
        typed_mean = self.stats_of_config(typed_config)["mean"]
        rows = []
        for group_size in range(1, self.get_num_modules()):
            # For each group size (freedom to type exactly N modules at once)
            # make a histogram of max overhead edges along each path
            # (Shows the number of paths that have 'really bad' overhead)
            print("Computing paths for group size '%s'" % group_size)
            cutoff = 1 + (self.get_num_modules() - group_size)
            paths = networkx.all_simple_paths(lattice, source=untyped_config, target=typed_config, cutoff=cutoff)
            weights = [self.max_weight(lattice, path) for path in paths if len(path) == 1 + cutoff]
            num_release_u = sum((1 for x in weights if x < (untyped_mean * constants.DELIVERABLE)))
            num_dev_u = sum((1 for x in weights if x < (untyped_mean * constants.USABLE)))
            num_x_u = sum((1 for x in weights if x < (untyped_mean * 15)))
            num_release_t = sum((1 for x in weights if x < (typed_mean * constants.DELIVERABLE)))
            num_dev_t = sum((1 for x in weights if x < (typed_mean * constants.USABLE)))
            num_x_t = sum((1 for x in weights if x < (typed_mean * 15)))
            num_paths = len(weights)
            rows.append(
                [str(cutoff), str(num_paths)]
                + [
                    "%s (%s\\%%)" % (x, round((x / num_paths) * 100, 2))
                    for x in [num_release_u, num_dev_u, num_x_u, num_release_t, num_dev_t, num_x_t]
                ]
            )
        print(
            latex.table(
                [
                    "Path length",
                    "Total",
                    "$<$ 2x untyped",
                    "$<$ 4x untyped",
                    "$<$ 15x untyped",
                    "$<$ 2x typed",
                    "$<$ 4x typed",
                    "$<$ 15x typed",
                ],
                rows,
            ),
            file=output_port,
        )

    ### Helpers ################################################################

    def of_tab(self, tabfile):
        """
            Initialize `stats_by_config` table from the spreadsheet `tabfile`
        """
        util.fold_file(
            tabfile, None, lambda acc, row: self.set_stats(row[0], util.stats_of_row([int(v) for v in row[1:]]))
        )

    def of_rktd(self, rktdfile):
        """ (-> Path-String Path-String)
            Input: a .rktd file; the results of running the benchmarks.
            Output: the string name of a newly-generated .tab file
                    (a more human-readable and Python-parse-able version of the .rktd)
        """
        # We have a Racket script to generate the .tab file,
        # make sure it exists.
        print("Parsing a .tab file from the raw source '%s'" % rktdfile)
        sexp_to_tab = shell.find_file("sexp-to-tab.rkt")
        if not sexp_to_tab:
            raise ValueError("Could not access 'sexp_to_tab' script. Cannot parse '%s'." % rktdfile)
        shell.execute("racket %s %s" % (sexp_to_tab, rktdfile))
        # Strip the suffix from the input file, replace with .tab
        return "%s.tab" % rktdfile.rsplit(".", 1)[0]

    def make_lattice(self, transitivity=1):
        """
            Create a try-everything lattice showing all the ways of moving
             from untyped to typed.
            Args:
            - configs : Iterable of all configurations (nodes) to include.
                        Must include an untyped and a typed configuration.
        """
        g = networkx.DiGraph()
        for cfg in self.all_configurations():
            g.add_node(cfg)
            w = self.stats_of_config(cfg)["mean"]
            for prev in config.previous_iter(cfg, transitivity):
                # SWAG networkx ignore duplicates SWAGSWAGS
                g.add_edge(prev, cfg, weight=w)
        return g

    def max_weight(self, lattice, path):
        """
            Return the max weight of edges along a networkx path.
            2015-05-07: really, this could just be MAX of cached sample stats
        """
        acc = 0
        prev = None
        for node in path:
            if prev is not None:
                acc = max(acc, lattice[prev][node]["weight"])
            prev = node
        return acc