diff --git a/h2/graham.py b/h2/graham.py index 22efc0f..3863844 100644 --- a/h2/graham.py +++ b/h2/graham.py @@ -28,18 +28,17 @@ def graham_scan(points): sorted_points = sorted(points) UH = sorted_points[:2] - with Profiler("iterating upper hull", excluded=("calculating sidedness",)): + with Profiler("iterating points", excluded=("calculating sidedness",)): for s in sorted_points[2:]: while len(UH) > 1 and (sidedness(UH[-2], UH[-1], s) != Side.ABOVE): del UH[-1] UH.append(s) - with Profiler("reversing list"): - reversed_list = list(reversed(sorted_points)) - reversed_list.append(UH[0]) + reversed_list = list(reversed(sorted_points)) + reversed_list.append(UH[0]) LH = reversed_list[:2] - with Profiler("iterating lower hull", excluded=("calculating sidedness",)): + with Profiler("iterating points", excluded=("calculating sidedness",)): for s in reversed_list[2:]: while len(LH) > 1 and (sidedness(LH[-2], LH[-1], s) != Side.ABOVE): del LH[-1] diff --git a/h2/tmptest.py b/h2/tmptest.py index 0d722e9..fb13b82 100644 --- a/h2/tmptest.py +++ b/h2/tmptest.py @@ -10,6 +10,9 @@ from profile import Profiler from quick_hull import quick_hull import os.path +import matplotlib.pyplot as plt + + #random.seed(1337_420) TimedResult = namedtuple("TimedResult", "algorithm points running_time") @@ -96,9 +99,9 @@ def do_one_profile(num_points): points = {util.gen_point(0, 100) for _ in range(num_points)} tests = [ - #("graham_scan", graham_scan), + ("graham_scan", graham_scan), #("gift_wrapper", rapper), - #("quick_hull", quick_hull), + ("quick_hull", quick_hull), ("mbc", mbc), ("mbc2", mbc2), ("mbc_no_shuffle", mbc_no_shuffle), @@ -135,38 +138,94 @@ def do_one_profile(num_points): return results -def do_profile(): - num_points = 60_000 - #results = do_one_profile(num_points) - results = {"mbc": {"times": {"finding median": 0.006870746612548828, "partitioning set": 0.066436767578125, "flipping constraints": 0.13354206085205078, "shuffling constraints": 0.08272123336791992, "solving LP": 0.13545918464660645, "finding bridge points": 0.06052136421203613, "pruning between line points": 0.04519915580749512, "mbc": 0.5643129348754883, "other": 0.033562421798706055}, "total": 0.5643129348754883, "total_profiled": 0.5307505130767822, "unaccounted": 0.033562421798706055}, "mbc2": {"times": {"extra pruning step": 0.15866398811340332, "finding median": 0.00189971923828125, "partitioning set": 0.022511959075927734, "flipping constraints": 0.04117441177368164, "shuffling constraints": 0.030447006225585938, "solving LP": 0.05805325508117676, "finding bridge points": 0.025882959365844727, "pruning between line points": 0.014936208724975586, "mbc2": 0.3658268451690674, "other": 0.01225733757019043}, "total": 0.3658268451690674, "total_profiled": 0.35356950759887695, "unaccounted": 0.01225733757019043}, "mbc_no_shuffle": {"times": {"finding median": 0.006849050521850586, "partitioning set": 0.06539726257324219, "flipping constraints": 0.13605880737304688, "solving LP": 0.06955385208129883, "finding bridge points": 0.06419634819030762, "pruning between line points": 0.044390201568603516, "mbc_no_shuffle": 0.397723913192749, "other": 0.011278390884399414}, "total": 0.397723913192749, "total_profiled": 0.3864455223083496, "unaccounted": 0.011278390884399414}, "mbc2_no_shuffle": {"times": {"extra pruning step": 0.16416001319885254, "finding median": 0.002000570297241211, "partitioning set": 0.022954702377319336, "flipping constraints": 0.0455164909362793, "solving LP": 0.02709197998046875, "finding bridge points": 0.022936582565307617, "pruning between line points": 0.017188310623168945, "mbc2_no_shuffle": 0.30688953399658203, "other": 0.005040884017944336}, "total": 0.30688953399658203, "total_profiled": 0.3018486499786377, "unaccounted": 0.005040884017944336}} - print("================== RESULTS ==================") - print(json.dumps(results)) - +def plot_mbc(results, ax): algorithms = list(results.keys()) - steps = ( "other", "finding median", "partitioning set", "flipping constraints", - "solving 2D", + "solving LP", "finding bridge points", "pruning between line points", "shuffling constraints", "extra pruning step", ) - - data = [[result["times"].get(step, 0) * 1000 for result in results.values()] - for step in steps] - - util.stacked_bar(data=data, + util.stacked_bar(ax=ax, + data=[[result["times"].get(step, 0) * 1000 for result in results.values()] for step in steps], series_labels=steps, category_labels=algorithms, - show_values=True, - value_format="{:.1f}", - y_label="time (ms)", - grid=False, - reverse=False) + value_format="{:.1f}") + + +def plot_graham(result, ax): + steps = ( + "other", + "sorting points", + "iterating points", + "calculating sidedness", + ) + util.stacked_bar(ax=ax, + data=[[result["times"][step] * 1000] for step in steps], + series_labels=steps, + category_labels=["graham scan"], + value_format="{:.1f}") + + +def plot_gift(result, ax): + steps = ( + "other", + "calculating angle", + "calculating vector", + ) + util.stacked_bar(ax=ax, + data=[[result["times"][step] * 1000] for step in steps], + series_labels=steps, + category_labels=["gift wrapper"], + value_format="{:.1f}") + + +def plot_quick(result, ax): + steps = ( + "other", + "partitioning set", + "finding farthest point from line", + ) + util.stacked_bar(ax=ax, + data=[[result["times"][step] * 1000] for step in steps], + series_labels=steps, + category_labels=["quickhull"], + value_format="{:.1f}") + + +def do_profile(): + num_points = 60_000 + results = do_one_profile(num_points) + + print("================== RESULTS ==================") + print(json.dumps(results)) + + fig, (ax1, ax2, ax3) = plt.subplots(ncols=3, sharex=False, sharey=True, gridspec_kw={"width_ratios": (1, 1, 4)}) + + fig.add_subplot(111, frameon=False) + plt.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off') + + plt.ylabel("time (ms)") + + plt.subplots_adjust(wspace=0) + + plot_graham(results["graham_scan"], ax1) + + #plot_gift(results["gift_wrapper"], ax2) + + plot_quick(results["quick_hull"], ax2) + + plot_mbc({alg: data + for alg, data in results.items() + if alg.startswith("mbc")}, + ax3) + + plt.show() if __name__ == '__main__': diff --git a/h2/util.py b/h2/util.py index 7c10c26..a547726 100644 --- a/h2/util.py +++ b/h2/util.py @@ -134,9 +134,9 @@ class Side(Enum): BELOW = auto() -def stacked_bar(data, series_labels, category_labels=None, - show_values=False, value_format="{}", y_label=None, - grid=True, reverse=False): +def stacked_bar(ax, data, series_labels, category_labels=None, + show_values=True, value_format="{}", y_label=None, + grid=False, reverse=False): """ Plots a stacked bar chart with the data and labels provided (https://stackoverflow.com/a/50205834). @@ -171,28 +171,32 @@ def stacked_bar(data, series_labels, category_labels=None, category_labels = reversed(category_labels) for i, row_data in enumerate(data): - axes.append(plt.bar(ind, row_data, bottom=cum_size, + axes.append(ax.bar(ind, row_data, bottom=cum_size, label=series_labels[i])) cum_size += row_data if category_labels: + plt.sca(ax) plt.xticks(ind, category_labels) if y_label: plt.ylabel(y_label) - plt.legend() + ax.legend() + # Reverse legend (https://stackoverflow.com/a/34576778) + handles, labels = ax.get_legend_handles_labels() + ax.legend(handles[::-1], labels[::-1]) if grid: - plt.grid() + ax.grid() if show_values: for axis in axes: for bar in axis: w, h = bar.get_width(), bar.get_height() if h != 0: - plt.text(bar.get_x() + w/2, bar.get_y() + h/2, + ax.text(bar.get_x() + w/2, bar.get_y() + h/2, value_format.format(h), ha="center", va="center") - plt.show() + #plt.show()