def main(tabfile, dgraph):
    """ (-> Path-String GraphDict Result)
        Input: a .tab file, an overall summary of running all configurations
               of a project.
        Output: a Result object.
    """
    print("Collecting results from ground truth data '%s'" % tabfile)
    fname = util.strip_suffix(tabfile).rsplit("/", 1)[-1]
    num_modules = count_modules(tabfile)
    num_configs = 2 ** num_modules
    print("Project contains %s modules (%s configurations)" % (num_modules, num_configs))
    u_raw = all_cells_matching(tabfile, config.is_untyped)
    g_raw = all_cells_matching(tabfile, lambda x: not (config.is_typed(x) or config.is_untyped(x)))
    t_raw = all_cells_matching(tabfile, config.is_typed)
    ugt_violin = plot.violin([u_raw, g_raw, t_raw]
                            ,"%s_untyped-vs-gradual-vs-typed" % fname
                            ,"Configuration"
                            ,"Runtime (ms)"
                            ,xlabels=["untyped","gradual\n(all configs)","typed"]) if u_raw and g_raw and t_raw else None
    # Collect absolute BEST and WORST times+configs
    ten_percent = max(3, min(10, int(0.10 * num_configs)))
    best_cfg_and_times  = best_rows(tabfile
                        ,ten_percent
                        ,lambda acc,tmp: tmp[1] < acc[1]
                        ,lambda row:(row[0], int(statistics.mean([int(x) for x in row[1::]]))))
    worst_cfg_and_times = best_rows(tabfile
                        ,ten_percent
                        ,lambda acc,tmp: acc[1] < tmp[1]
                        ,lambda row:(row[0], int(statistics.mean([int(x) for x in row[1::]]))))
    stats = {
        "title"    : fname
        ,"runs"    : count_runs(tabfile)
        ,"graph"   : dgraph
        ,"ugt"     : {"img" : ugt_violin
                     ,"summary" : {"untyped" : basic_row_stats(u_raw)
                                  ,"gradual" : basic_row_stats(g_raw)
                                  ,"typed"   : basic_row_stats(t_raw)}}
        ,"best"    : [config.basic_stats(v[0], v[1], dgraph)
                      for v in best_cfg_and_times if v is not None]
        ,"worst"   : [config.basic_stats(v[0], v[1], dgraph)
                      for v in worst_cfg_and_times if v is not None]
        ,"bucketed": plot.violin(bucketize(tabfile, config.num_typed_modules, num_modules)
                           ,"%s_by-typed-modules" % fname
                           ,"Number of Typed Modules"
                           ,"Runtime (ms)"
                           ,positions=range(0, 1+num_modules)
                           ,xlabels=range(0, 1+num_modules))
        ,"fixed"   : [plot.double_violin([bucketize(tabfile, config.num_typed_modules, num_modules, pred=lambda cfg: config.untyped_at(cfg, v[0]))
                                    ,bucketize(tabfile, config.num_typed_modules, num_modules, pred=lambda cfg: config.typed_at(cfg, v[0]))]
                                    ,"%s_fixing-%s" % (fname, k)
                                    ,"Number of Typed Modules"
                                    ,"Runtime (ms)"
                                    ,legend=["%s is untyped" % k
                                            ,"%s is typed" % k])
                      for (k,v) in sorted(dgraph.items(), key=lambda item:item[1][0])]
    }
    return stats
 def graph_absolute_runtimes(self, preds, xtitle, xlabels, output, title=None):
     """
         Plot the absolute running times of the configurations picked by
         each predicate in `preds`.
         (The number of columns in the result is equal to the size of `preds`)
     """
     columns = [self.stats_of_predicate(pred)["raw"]
                for pred in preds]
     # TODO filter empty columns ?
     return plot.violin(columns
                        ,title or "%s absolute runtimes" % self.get_project_name()
                        ,xtitle
                        ,"Runtime (ms)"
                        ,xlabels=xlabels
                        ,output="%s/%s" % (self.output_dir, output))