diff options
Diffstat (limited to 'src/plots.py')
| -rw-r--r-- | src/plots.py | 414 |
1 files changed, 248 insertions, 166 deletions
diff --git a/src/plots.py b/src/plots.py index a22a201..3228e5b 100644 --- a/src/plots.py +++ b/src/plots.py @@ -16,12 +16,13 @@ # along with allocbench. If not, see <http://www.gnu.org/licenses/>. """Plot different graphs from allocbench results""" -import os -import traceback - +import copy +import itertools import matplotlib import matplotlib.pyplot as plt import numpy as np +import os +import traceback import src.globalvars from src.util import print_debug, print_warn @@ -29,6 +30,38 @@ from src.util import print_debug, print_warn # This is useful when evaluating strings in the plot functions. str(np.NaN) == "nan" nan = np.NaN +DEFAULT_PLOT_OPTIONS = { + 'plot': { + 'marker': '.', + 'linestyle': '-', + }, + 'errorbar': { + 'marker': '.', + 'linestyle': '-', + 'yerr': True, + }, + 'bar': { + 'yerr': True, + } +} + +DEFAULT_FIG_OPTIONS = { + 'plot': { + 'legend': True, + 'legend_pos': 'best', + 'autoticks': True, + }, + 'errorbar': { + 'legend': True, + 'legend_pos': 'best', + 'autoticks': True, + }, + 'bar': { + 'legend': True, + 'legend_pos': 'lower center', + 'autoticks': False, + } +} def _get_alloc_color(bench, alloc): """Populate all not set allocator colors with matplotlib 'C' colors""" @@ -81,197 +114,246 @@ def _get_y_data(bench, expression, allocator, perms, stat="mean", scale=None): return y_data +def _create_plot_options(plot_type, **kwargs): + """ + Create a plot options dictionary. + + Parameters + ---------- + plot_type : str + The plot type for which the options should be created. + Possible values: {'bar', 'errorbar', 'plot'} + + **kwargs : plot properties, optional + *kwargs* are used to specify properties like a line label (for + auto legends), linewidth, antialiasing, marker face color. + + Returns + ------- + options : dict + Dict holding the specified options and all default values for plot type + """ -def _save_figure(fig, - fig_name, - sumdir='', - file_ext=src.globalvars.summary_file_ext): - fig_path = os.path.join(sumdir, f"{fig_name}.{file_ext}") - if file_ext == "tex": - import tikzplotlib - tikzplotlib.save(fig_path) - else: - fig.savefig(fig_path) + options = copy.deepcopy(DEFAULT_PLOT_OPTIONS[plot_type]) + for key, value in kwargs.items(): + options[key] = value + return options -def plot_single_arg(bench, - yval, - ylabel="y-label", - xlabel="x-label", - autoticks=True, - title="default title", - file_postfix="", - sumdir="", - arg="", - scale=None, - file_ext=src.globalvars.summary_file_ext): - """plot line graphs for each permutation of the benchmark's command arguments""" +def _create_figure_options(plot_type, fig_label, **kwargs): + """ + Create a figure options dictionary + + Parameters + ---------- + plot_type : str + The plot type for which the options should be created. + Possible values: {'bar', 'errorbar', 'plot'} + + **kwargs : figure properties, optional + *kwargs* are used to specify properties like legends, legend position, + x-/ and ylabel, and title. + + Returns + ------- + options : dict + Dict holding the specified options and all default values for plot type + """ - args = bench.results["args"] - allocators = bench.results["allocators"] + options = copy.deepcopy(DEFAULT_FIG_OPTIONS[plot_type]) - arg = arg or list(args.keys())[0] + options['fig_label'] = fig_label - fig_name = f'{bench.name}.{file_postfix}' - fig = plt.figure(fig_name) + for key, value in kwargs.items(): + options[key] = value - if not autoticks: - x_vals = list(range(1, len(args[arg]) + 1)) - else: - x_vals = args[arg] + return options + +def _plot(bench, + allocators, + y_expression, + x_data, + perms, + plot_type, + plot_options, + fig_options, + scale=None, + file_postfix="", + sumdir="", + file_ext=src.globalvars.summary_file_ext): + """ + Create a plot for a given expression + Parameters + ---------- + + Returns + ------- + figure : :rc:`~matplotlib.figure.Figure` + The new :rc:`.Figure` instance wrapping our plot. + + Notes + ----- + If you are creating many figures, make sure you explicitly call + :rc:`.pyplot.close` on the figures you are not using, because this will + enable pyplot to properly clean up the memory. + """ + + fig = plt.figure(fig_options['fig_label']) for allocator in allocators: - y_vals = _get_y_data(bench, - yval, + y_data = _get_y_data(bench, + y_expression, allocator, - bench.iterate_args(args=args), + perms, stat='mean', scale=scale) - plt.plot(x_vals, - y_vals, - marker='.', - linestyle='-', - label=allocator, - color=_get_alloc_color(bench, allocator)) - - plt.legend(loc="best") - if not autoticks: - plt.xticks(x_vals, args[arg]) - label_substitutions = vars() - label_substitutions.update(vars(bench)) - plt.xlabel(xlabel.format(**label_substitutions)) - plt.ylabel(ylabel.format(**label_substitutions)) - plt.title(title.format(**label_substitutions)) - _save_figure(fig, fig_name, sumdir, file_ext) - plt.close(fig) + if plot_options.get('yerr', False): + plot_options['yerr'] = _get_y_data(bench, + y_expression, + allocator, + perms, + stat='mean') + try: + plot_func = getattr(plt, plot_type) + except AttributeError: + print_debug(f'Unknown plot type: {plot_type}') + raise + + _x_data = x_data + if not fig_options['autoticks']: + _x_data = list(range(1, len(x_data) + 1)) + + plot_func(_x_data, + y_data, + label=allocator, + color=_get_alloc_color(bench, allocator), + **plot_options) + + if fig_options['legend']: + plt.legend(loc=fig_options['legend_pos']) + + if not fig_options['autoticks']: + plt.xticks(_x_data, x_data) + + plt.xlabel(fig_options['xlabel']) + plt.ylabel(fig_options['ylabel']) + plt.title(fig_options['title']) + + fig_path = os.path.join(sumdir, f'{fig_options["fig_label"]}.{file_ext}') + if file_ext == 'tex': + import tikzplotlib + tikzplotlib.save(fig_path) + else: + fig.savefig(fig_path) return fig +def plot(bench, + y_expression, + plot_type='errorbar', + x_args=None, + scale=None, + plot_options=None, + fig_options=None, + file_postfix="", + sumdir="", + file_ext=src.globalvars.summary_file_ext): + """ + Create plots for a given expression for the y axis. + + Parameters + ---------- + + y_expression : str + + plot_type : str, optional, default='errorbar' + The plot type for which the options should be created. + Possible values: {'bar', 'errorbar', 'plot'} + + x_args : [str], optional, default=None + The benchmark arguments for which a plot should be created. + If not provided, defaults to :rc:`bench.arguments.keys()` + + scale : str, optional, default=None + Name of the allocator which should be used to normalize the results. + + plot_options : dict, optional, default None + Dictionary containing plot options which should be passed to the plot + type function. If not provided the default plot type options are used. + Possible options: + * yerr: bool - Plot the standard deviation as errorbars + * marker: str - Style of the used markers + * line: str - Style of the drawn lines + + fig_options : dict, optional, default None + Dictionary containing figure options. + If not provided the default plot type options are used. + Possible options: + * ylabel : str - The label of the y axis. + * xlabel : str - The label of the x axis. + * title : str - The title of the plot. + * legend : bool - Should the plot have a legend. + * legend_pos : str - Location of the legend. + For possible values see :rc:`help(matplotlib.pyploy.legend)`. + * autoticks : bool - Let matplotlib set the xticks automatically. + + file_postfix: str, optional, default="" + Postfix which is appended to the plot's file name. + + sumdir : path or str, optional, default="" + Directory where the plot should be saved. If not provided defaults + to the current working directory. + + file_ext : str, optional, default=:rc:`src.globalvars.summary_file_ext` + File extension of the saved plot. If not provided defaults to the + value of :rc:`src.globalvars.summary_file_ext` -def barplot_single_arg(bench, - yval, - ylabel="y-label", - xlabel="x-label", - title="default title", - file_postfix="", - sumdir="", - arg="", - scale=None, - file_ext=src.globalvars.summary_file_ext, - yerr=True): - """plot bar plots for each permutation of the benchmark's command arguments""" + """ args = bench.results["args"] allocators = bench.results["allocators"] - nallocators = len(allocators) - if arg: - arg = args[arg] - elif args.keys(): - arg = args[list(args.keys())[0]] - else: - arg = [""] + if not x_args: + x_args = args - narg = len(arg) + for loose_arg in x_args: + x_data = args[loose_arg] - fig_name = f'{bench.name}.{file_postfix}' - fig = plt.figure(fig_name) + fixed_args = [[(k, v) for v in args[k]] for k in args if k != loose_arg] + for fixed_part in itertools.product(*fixed_args): + fixed_part = {k:v for k, v in fixed_part} - for i, allocator in enumerate(allocators): - x_vals = list(range(i, narg * (nallocators + 1), nallocators + 1)) - y_vals = _get_y_data(bench, - yval, - allocator, - bench.iterate_args(args=args), - stat='mean', - scale=scale) - y_errs = None - if yerr: - y_vals = _get_y_data(bench, - yval, - allocator, - bench.iterate_args(args=args), - stat='std') - - plt.bar(x_vals, - y_vals, - width=1, - label=allocator, - yerr=y_errs, - color=_get_alloc_color(bench, allocator)) - - plt.legend(loc="best") - plt.xticks( - list( - range(int(np.floor(nallocators / 2)), narg * (nallocators + 1), - nallocators + 1)), arg) + fixed_part_str = ".".join([f'{k}={v}' for k, v in fixed_part.items()]) + fig_label = f'{bench.name}.{fixed_part_str}.{file_postfix}' - label_substitutions = vars() - label_substitutions.update(vars(bench)) - plt.xlabel(xlabel.format(**label_substitutions)) - plt.ylabel(ylabel.format(**label_substitutions)) - plt.title(title.format(**label_substitutions)) - - _save_figure(fig, fig_name, sumdir, file_ext) - plt.close(fig) + plot_options = _create_plot_options(plot_type, **plot_options or {}) + substitutions = vars() + substitutions.update(vars(bench)) + for option, value in fig_options.items(): + if isinstance(value, str): + fig_options[option] = value.format(**substitutions) -def plot_fixed_arg(bench, - yval, - ylabel="y-label", - xlabel="{loose_arg}", - autoticks=True, - title="default title", - file_postfix="", - sumdir="", - fixed=None, - file_ext=src.globalvars.summary_file_ext, - scale=None): + # plot specific defaults + fig_options.setdefault("ylabel", y_expression) + fig_options.setdefault("xlabel", loose_arg) + fig_options.setdefault("titel", fig_label) - args = bench.results["args"] - allocators = bench.results["allocators"] + fig_options = _create_figure_options( + plot_type, + fig_label, + **fig_options or {}, + ) - for arg in fixed or args: - loose_arg = [a for a in args if a != arg][0] - - if not autoticks: - x_vals = list(range(1, len(args[loose_arg]) + 1)) - else: - x_vals = args[loose_arg] - - for arg_value in args[arg]: - fig_name = f'{bench.name}.{arg}.{arg_value}.{file_postfix}' - fig = plt.figure(fig_name) - - for allocator in allocators: - y_vals = _get_y_data(bench, - yval, - allocator, - bench.iterate_args_fixed({arg: arg_value}, - args=args), - stat='mean', - scale=scale) - - plt.plot(x_vals, - y_vals, - marker='.', - linestyle='-', - label=allocator, - color=_get_alloc_color(bench, allocator)) - - plt.legend(loc="best") - if not autoticks: - plt.xticks(x_vals, args[loose_arg]) - - label_substitutions = vars() - label_substitutions.update(vars(bench)) - plt.xlabel(xlabel.format(**label_substitutions)) - plt.ylabel(ylabel.format(**label_substitutions)) - plt.title(title.format(**label_substitutions)) - - _save_figure(fig, fig_name, sumdir, file_ext) - plt.close(fig) + _plot(bench, + allocators, + y_expression, + x_data, + list(bench.iterate_args(args=args, fixed=fixed_part)), + plot_type, + plot_options, + fig_options) def export_facts_to_file(bench, comment_symbol, output_file): @@ -392,7 +474,7 @@ def write_best_doublearg_tex_table(bench, cell_text = [] for arg_value in rows: row = [] - for perm in bench.iterate_args_fixed({row_arg: arg_value}, args=args): + for perm in bench.iterate_args(args=args, fixed={row_arg: arg_value}): best = [] best_val = None for allocator in allocators: |
