Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 222 additions & 38 deletions plotting/plot_latest_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Copy Markdown
Collaborator

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.

Copy link
Copy Markdown
Author

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.

Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Author

@AdiBak AdiBak Dec 3, 2025

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:

  • 1-3 benchmarks: 1 row, 1-3 columns
  • 4-6 benchmarks: 2 rows, 3 columns (same as before)
  • 7-9 benchmarks: 3 rows, 3 columns
  • 10-12 benchmarks: 3 rows, 4 columns
  • More than 12: Calculate optimal grid

Let me know what you think. Thanks!


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,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to have ylabel be "Compiled Multi-Qubit Gate Count" to be clear it doesn't include single qubit gates.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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(
Expand All @@ -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)",
Expand All @@ -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():
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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"?

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.