-
Notifications
You must be signed in to change notification settings - Fork 9
Improve plotting: separate subplots per benchmark + add gate ratio metric #174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
78b0983
4ced7f1
e1c51b3
298c86d
e27638c
a3ce8c4
e5ac54a
f68b089
da1e16e
2a24b80
96e434c
10adf4f
4f02d56
a9b0f57
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,86 +3,248 @@ | |
| import sys | ||
| import pandas as pd | ||
| import matplotlib.pyplot as plt | ||
| import numpy as np | ||
| from ucc_bench.results import SuiteResultsDatabase, to_df_timing, to_df_simulation | ||
|
|
||
| from shared import calculate_abs_relative_error, get_compiler_colormap | ||
|
|
||
| BAR_WIDTH = 0.2 | ||
|
|
||
|
|
||
| def generate_plot( | ||
| def generate_all_in_one_plots( | ||
| df: pd.DataFrame, | ||
| plot_configs: list[dict], | ||
| latest_date: str, | ||
| out_path: Path, | ||
| use_pdf: bool = False, | ||
| show_raw_gates: bool = False, | ||
| ): | ||
| """Generic plotting function to create bar charts for benchmark data.""" | ||
| """Generate all-in-one plots with all circuits in a single plot. | ||
|
|
||
| Creates grouped bar charts where all benchmarks are shown in one plot, | ||
| with bars grouped by benchmark and colored by compiler. | ||
| """ | ||
| # Configure matplotlib for LaTeX output if PDF export is requested | ||
| if use_pdf: | ||
| plt.rcParams.update( | ||
| { | ||
| "text.usetex": True, # for matching math & fonts (optional) | ||
| "text.usetex": True, | ||
| "font.family": "serif", | ||
| } | ||
| ) | ||
|
|
||
| circuit_names = sorted(df["benchmark_id"].unique()) | ||
| x_positions = range(len(circuit_names)) | ||
| circuit_name_to_index = {name: i for i, name in enumerate(circuit_names)} | ||
| benchmarks = sorted(df["benchmark_id"].unique()) | ||
| compilers = sorted(df["compiler"].unique()) | ||
| color_map = get_compiler_colormap() | ||
|
|
||
| num_plots = len(plot_configs) | ||
| fig, axes = plt.subplots(1, num_plots, figsize=(7 * num_plots, 7), squeeze=False) | ||
| axes = axes.flatten() | ||
| # Create separate figures for each metric | ||
| for config in plot_configs: | ||
| fig, ax = plt.subplots(figsize=(12, 6)) | ||
|
|
||
| # Prepare data for grouped bar chart | ||
| x = np.arange(len(benchmarks)) | ||
| width = 0.8 / len(compilers) # Width of bars, adjusted for number of compilers | ||
|
|
||
| compilers = df["compiler"].unique() | ||
| for i, compiler_name in enumerate(compilers): | ||
| grp = df[df["compiler"] == compiler_name] | ||
| grp_indices = grp["benchmark_id"].map(circuit_name_to_index) | ||
| bar_positions = [idx + i * BAR_WIDTH for idx in grp_indices] | ||
| # Create bars for each compiler | ||
| for i, compiler in enumerate(compilers): | ||
| values = [] | ||
| for benchmark in benchmarks: | ||
| row = df[ | ||
| (df["benchmark_id"] == benchmark) & (df["compiler"] == compiler) | ||
| ] | ||
| if not row.empty: | ||
| values.append(row[config["y_col"]].values[0]) | ||
| else: | ||
| values.append(np.nan) # Use NaN to skip missing data | ||
|
|
||
| for ax, config in zip(axes, plot_configs): | ||
| offset = (i - (len(compilers) - 1) / 2) * width | ||
| ax.bar( | ||
| bar_positions, | ||
| grp[config["y_col"]], | ||
| width=BAR_WIDTH, | ||
| label=compiler_name, | ||
| color=color_map.get(compiler_name), | ||
| x + offset, | ||
| values, | ||
| width, | ||
| label=compiler, | ||
| color=color_map.get(compiler, "#4C72B0"), | ||
| ) | ||
|
|
||
| for ax, config in zip(axes, plot_configs): | ||
| ax.set_title(f"{config['title']} (Date: {latest_date})") | ||
| ax.set_xlabel("Circuit Name") | ||
| # Set x-axis labels | ||
| if show_raw_gates: | ||
| # Create two-line labels: benchmark name + raw gate count | ||
| labels = [] | ||
| for benchmark in benchmarks: | ||
| # Get raw gate count (should be same for all compilers for a given benchmark) | ||
| sample_row = df[df["benchmark_id"] == benchmark].iloc[0] | ||
| raw_gates = int(sample_row["raw_multiq_gates"]) | ||
| labels.append(f"{benchmark}\n({raw_gates} gates)") | ||
| else: | ||
| labels = benchmarks | ||
|
|
||
| ax.set_xticks(x) | ||
| ax.set_xticklabels(labels, rotation=30, ha="right") | ||
| ax.set_ylabel(config["ylabel"]) | ||
| ax.set_xticks(x_positions) | ||
| ax.set_xticklabels(circuit_names, rotation=75, ha="right") | ||
| ax.set_yscale("log") | ||
| ax.legend(title="Compiler") | ||
| ax.set_title(f"{config['title']} (Date: {latest_date})", fontsize=16) | ||
| ax.legend(title="Compiler", bbox_to_anchor=(1.05, 1), loc="upper left") | ||
|
|
||
| if config.get("use_log_scale", True): | ||
| ax.set_yscale("log") | ||
| # Reduce busyness of log scale | ||
| ax.tick_params(axis="y", which="minor", length=0) | ||
| ax.yaxis.set_major_locator(plt.LogLocator(base=10, numticks=4)) | ||
|
|
||
| plt.tight_layout() | ||
| metric_name = config["y_col"].replace("_", "-") | ||
| metric_out_path = ( | ||
| out_path.parent / f"{out_path.stem}_{metric_name}{out_path.suffix}" | ||
| ) | ||
| print(f"Saving plot to {metric_out_path}") | ||
| fig.savefig(metric_out_path, dpi=300, bbox_inches="tight") | ||
| plt.close(fig) | ||
|
|
||
|
|
||
| def generate_subplots( | ||
| df: pd.DataFrame, | ||
| plot_configs: list[dict], | ||
| latest_date: str, | ||
| out_path: Path, | ||
| use_pdf: bool = False, | ||
| ): | ||
| """Generate subplots with separate subplot per benchmark. | ||
|
|
||
| Generic function for creating subplot layouts for both compilation and simulation benchmarks. | ||
| """ | ||
| # Configure matplotlib for LaTeX output if PDF export is requested | ||
| if use_pdf: | ||
| plt.rcParams.update( | ||
| { | ||
| "text.usetex": True, # for matching math & fonts (optional) | ||
| "font.family": "serif", | ||
| } | ||
| ) | ||
|
|
||
| benchmarks = sorted(df["benchmark_id"].unique()) | ||
| n_benchmarks = len(benchmarks) | ||
|
|
||
| # Calculate grid size dynamically based on number of benchmarks | ||
| # Prefer 3 columns for readability, but adjust as needed | ||
| if n_benchmarks <= 3: | ||
| ncols = n_benchmarks | ||
| nrows = 1 | ||
| elif n_benchmarks <= 6: | ||
| ncols = 3 | ||
| nrows = 2 | ||
| elif n_benchmarks <= 9: | ||
| ncols = 3 | ||
| nrows = 3 | ||
| elif n_benchmarks <= 12: | ||
| ncols = 4 | ||
| nrows = 3 | ||
| else: | ||
| # For more benchmarks, calculate optimal grid | ||
| ncols = int(np.ceil(np.sqrt(n_benchmarks))) | ||
| nrows = int(np.ceil(n_benchmarks / ncols)) | ||
|
|
||
| # Create separate figures for each metric | ||
| for config in plot_configs: | ||
| fig, axes = plt.subplots( | ||
| nrows, ncols, figsize=(5 * ncols, 4 * nrows), squeeze=False | ||
| ) | ||
| axes = axes.flatten() | ||
| color_map = get_compiler_colormap() | ||
|
|
||
| for i, ax in enumerate(axes): | ||
| if i < n_benchmarks: | ||
| benchmark = benchmarks[i] | ||
| sub = df[df["benchmark_id"] == benchmark] | ||
|
|
||
| # Extract values for each compiler | ||
| values = sub[config["y_col"]].values | ||
| compiler_names = sub["compiler"].values | ||
|
|
||
| # Create bars | ||
| x_positions = np.arange(len(compiler_names)) | ||
| ax.bar( | ||
| x_positions, | ||
| values, | ||
| color=[ | ||
| color_map.get(compiler, "#4C72B0") | ||
| for compiler in compiler_names | ||
| ], | ||
| width=0.5, | ||
| ) | ||
|
|
||
| plt.tight_layout() | ||
| print(f"Saving plot to {out_path}") | ||
| fig.savefig(out_path, dpi=300, bbox_inches="tight") | ||
| plt.close(fig) | ||
| ax.set_xticks(x_positions) | ||
| ax.set_xticklabels(compiler_names, rotation=30, ha="right") | ||
| ax.set_title(f"Benchmark: {benchmark}") | ||
| ax.set_ylabel(config["ylabel"]) | ||
| # Use log scale only if specified in config (default to True for backwards compatibility) | ||
| if config.get("use_log_scale", True): | ||
| ax.set_yscale("log") | ||
|
|
||
| else: | ||
| ax.set_visible(False) | ||
|
|
||
| plt.suptitle(f"{config['title']} (Date: {latest_date})", fontsize=16) | ||
| plt.tight_layout(rect=[0, 0, 1, 0.96]) | ||
|
|
||
| # Save with metric-specific filename | ||
| metric_name = config["y_col"].replace("_", "-") | ||
| metric_out_path = ( | ||
| out_path.parent / f"{out_path.stem}_{metric_name}{out_path.suffix}" | ||
| ) | ||
| print(f"Saving plot to {metric_out_path}") | ||
| fig.savefig(metric_out_path, dpi=300, bbox_inches="tight") | ||
| plt.close(fig) | ||
|
|
||
|
|
||
| def plot_compilation( | ||
| df: pd.DataFrame, latest_date: str, out_path: Path, use_pdf: bool = False | ||
| ): | ||
| """Generates and saves plots for compilation benchmark data.""" | ||
| plot_configs = [ | ||
| df_comp = df.copy() | ||
| df_comp["compiled_ratio"] = ( | ||
| df_comp["compiled_multiq_gates"] / df_comp["raw_multiq_gates"] | ||
| ) | ||
|
|
||
| # All-in-one plots (replacing gate counts with compiled ratio) | ||
| all_in_one_configs = [ | ||
| { | ||
| "y_col": "compile_time", | ||
| "title": "Compiler Performance", | ||
| "ylabel": "Compile Time (s)", | ||
| "use_log_scale": True, | ||
| }, | ||
| { | ||
| "y_col": "compiled_ratio", | ||
| "title": "Compiled Gate Ratio", | ||
| "ylabel": "Compiled Gates / Raw Gates", | ||
| "use_log_scale": False, | ||
| }, | ||
| ] | ||
| generate_all_in_one_plots( | ||
| df_comp, all_in_one_configs, latest_date, out_path, use_pdf, show_raw_gates=True | ||
| ) | ||
|
|
||
| # Subplot versions (additional plots with all metrics) | ||
| subplot_configs = [ | ||
| { | ||
| "y_col": "compile_time", | ||
| "title": "Compiler Performance", | ||
| "ylabel": "Compile Time (s)", | ||
| "use_log_scale": True, | ||
| }, | ||
| { | ||
| "y_col": "compiled_multiq_gates", | ||
| "title": "Gate Counts", | ||
| "ylabel": "Compiled Gate Count", | ||
| "ylabel": "Compiled Multi-Qubit Gate Count", | ||
| "use_log_scale": True, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be better to have
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also noting this is a gap in the existing code already! |
||
| }, | ||
| { | ||
| "y_col": "compiled_ratio", | ||
| "title": "Compiled Gate Ratio", | ||
| "ylabel": "Compiled Gates / Raw Gates", | ||
| "use_log_scale": False, | ||
| }, | ||
| ] | ||
| generate_plot(df, plot_configs, latest_date, out_path, use_pdf) | ||
| # Save subplots with different filename to avoid overwriting | ||
| subplot_out_path = out_path.parent / f"{out_path.stem}_subplots{out_path.suffix}" | ||
| generate_subplots(df_comp, subplot_configs, latest_date, subplot_out_path, use_pdf) | ||
|
|
||
|
|
||
| def plot_simulation( | ||
|
|
@@ -97,7 +259,27 @@ def plot_simulation( | |
| df_sim["compiled_noisy"], df_sim["uncompiled_noisy"] | ||
| ) | ||
|
|
||
| plot_configs = [ | ||
| # All-in-one plots | ||
| all_in_one_configs = [ | ||
| { | ||
| "y_col": "rel_err_ideal", | ||
| "title": "Observable Error (Noiseless Sim)", | ||
| "ylabel": "Absolute Relative Error", | ||
| "use_log_scale": True, | ||
| }, | ||
| { | ||
| "y_col": "rel_err_noisy", | ||
| "title": "Observable Error (Noisy Sim)", | ||
| "ylabel": "Absolute Relative Error", | ||
| "use_log_scale": True, | ||
| }, | ||
| ] | ||
| generate_all_in_one_plots( | ||
| df_sim, all_in_one_configs, latest_date, out_path, use_pdf, show_raw_gates=False | ||
| ) | ||
|
|
||
| # Subplot versions (additional plots) | ||
| subplot_configs = [ | ||
| { | ||
| "y_col": "rel_err_ideal", | ||
| "title": "Observable Error (Noiseless Sim)", | ||
|
|
@@ -109,7 +291,9 @@ def plot_simulation( | |
| "ylabel": "Absolute Relative Error", | ||
| }, | ||
| ] | ||
| generate_plot(df_sim, plot_configs, latest_date, out_path, use_pdf) | ||
| # Save subplots with different filename to avoid overwriting | ||
| subplot_out_path = out_path.parent / f"{out_path.stem}_subplots{out_path.suffix}" | ||
| generate_subplots(df_sim, subplot_configs, latest_date, subplot_out_path, use_pdf) | ||
|
|
||
|
|
||
| def main(): | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is more a style nit, but the logscale for the prep_select and qv benchmarks has way more hashes labeled. Is there a nice way to make it less "busy"? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there are more than 6 benchmark results, does this silently just not show the additional plots? I'd consider either supporting arbitrary number of benchmark results, or at least asserting/erroring its not the hard coded 6 results.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point. An error could perhaps be raised when the number of benchmarks exceeds the 6 available subplots.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AdiBak -- I missed this earlier, but I would prefer if we made the number of subplots explicitly dependent on the number of benchmarks, rather than hard-coding it at 2 x 3
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! I made it dynamically determine how many rows and cols should exist, so essentially it'd follow this:
Let me know what you think. Thanks!