"""
Qiskit visualization strategy for QWARD.
"""
from typing import Dict, List, Optional
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from .base import VisualizationStrategy, PlotConfig, PlotMetadata, PlotType, PlotRegistry
from .constants import Plots
[docs]
class QiskitVisualizer(VisualizationStrategy):
"""Visualization strategy for QiskitMetrics with circuit structure and instruction analysis."""
# Class-level plot registry
PLOT_REGISTRY: PlotRegistry = {
Plots.Qiskit.CIRCUIT_STRUCTURE: PlotMetadata(
name=Plots.Qiskit.CIRCUIT_STRUCTURE,
method_name="plot_circuit_structure",
description="Visualizes the structure of quantum circuits including depth, width, and gate composition",
plot_type=PlotType.BAR_CHART,
filename="circuit_structure",
dependencies=["basic_metrics.depth", "basic_metrics.num_qubits"],
category="Structure Analysis",
),
Plots.Qiskit.GATE_DISTRIBUTION: PlotMetadata(
name=Plots.Qiskit.GATE_DISTRIBUTION,
method_name="plot_gate_distribution",
description="Shows the distribution of different gate types used in the circuit",
plot_type=PlotType.PIE_CHART,
filename="gate_distribution",
dependencies=["gate_metrics.gate_types"],
category="Gate Analysis",
),
Plots.Qiskit.INSTRUCTION_METRICS: PlotMetadata(
name=Plots.Qiskit.INSTRUCTION_METRICS,
method_name="plot_instruction_metrics",
description="Displays detailed instruction-level metrics and statistics",
plot_type=PlotType.BAR_CHART,
filename="instruction_metrics",
dependencies=[
"instruction_metrics.instruction_count",
"instruction_metrics.instruction_types",
],
category="Instruction Analysis",
),
Plots.Qiskit.CIRCUIT_SUMMARY: PlotMetadata(
name=Plots.Qiskit.CIRCUIT_SUMMARY,
method_name="plot_circuit_summary",
description="Comprehensive overview combining multiple circuit metrics",
plot_type=PlotType.BAR_CHART,
filename="circuit_summary",
dependencies=["basic_metrics", "gate_metrics", "instruction_metrics"],
category="Summary",
),
}
[docs]
@classmethod
def get_available_plots(cls) -> List[str]:
"""Return list of available plot names for this strategy."""
return list(cls.PLOT_REGISTRY.keys())
def __init__(
self,
metrics_dict: Dict[str, pd.DataFrame],
output_dir: str = "img",
config: Optional[PlotConfig] = None,
):
"""
Initialize the Qiskit visualization strategy.
Args:
metrics_dict: Dictionary containing QiskitMetrics data.
Expected key: "QiskitMetrics".
output_dir: Directory to save plots.
config: Plot configuration settings.
"""
super().__init__(metrics_dict, output_dir, config)
# Fetch data
self.qiskit_df = self.metrics_dict.get("QiskitMetrics")
if self.qiskit_df is None:
raise ValueError("'QiskitMetrics' data not found in metrics_dict.")
if self.qiskit_df.empty:
raise ValueError("QiskitMetrics DataFrame is empty.")
# Validate core columns
self._validate_data()
def _validate_data(self) -> None:
"""Validate that required columns exist in the data."""
required_basic_cols = [
"basic_metrics.depth",
"basic_metrics.width",
"basic_metrics.size",
"basic_metrics.num_qubits",
"basic_metrics.num_clbits",
]
self._validate_required_columns(self.qiskit_df, required_basic_cols, "QiskitMetrics data")
[docs]
def plot_circuit_structure(
self,
save: bool = False,
show: bool = False,
fig_ax_override: Optional[tuple[plt.Figure, plt.Axes]] = None,
title: Optional[str] = None,
) -> plt.Figure:
"""Plot basic circuit structure metrics."""
fig, ax, is_override = self._setup_plot_axes(fig_ax_override)
# Get basic structure metrics
structure_data = {
"Depth": self.qiskit_df["basic_metrics.depth"].iloc[0],
"Width": self.qiskit_df["basic_metrics.width"].iloc[0],
"Size": self.qiskit_df["basic_metrics.size"].iloc[0],
"Qubits": self.qiskit_df["basic_metrics.num_qubits"].iloc[0],
"Classical Bits": self.qiskit_df["basic_metrics.num_clbits"].iloc[0],
}
self._create_bar_plot_with_labels(
data=structure_data,
ax=ax,
title=title or "Circuit Structure",
xlabel="Metrics",
ylabel="Count",
value_format="int",
)
return self._finalize_plot(
fig=fig,
is_override=is_override,
save=save,
show=show,
filename="qiskit_circuit_structure",
)
[docs]
def plot_gate_distribution(
self,
save: bool = False,
show: bool = False,
fig_ax_override: Optional[tuple[plt.Figure, plt.Axes]] = None,
title: Optional[str] = None,
) -> plt.Figure:
"""Plot distribution of gate types."""
fig, ax, is_override = self._setup_plot_axes(fig_ax_override)
# Extract gate counts from count_ops columns
gate_counts = {}
for col in self.qiskit_df.columns:
if col.startswith("basic_metrics.count_ops."):
gate_name = col.replace("basic_metrics.count_ops.", "")
gate_counts[gate_name] = self.qiskit_df[col].iloc[0]
if not gate_counts:
self._show_no_data_message(
ax, title or "Gate Distribution", "No gate count data available"
)
return self._finalize_plot(
fig=fig,
is_override=is_override,
save=save,
show=show,
filename="qiskit_gate_distribution",
)
gate_series = pd.Series(gate_counts)
# Create pie chart for gate distribution
pie_result = ax.pie(
gate_series.values,
labels=gate_series.index,
autopct="%1.1f%%",
colors=self.config.color_palette[: len(gate_series)],
startangle=90,
)
# Extract autopct text objects if available
if len(pie_result) > 2:
autotexts = pie_result[2]
# Enhance text readability
for autotext in autotexts:
autotext.set_color("white")
autotext.set_fontweight("bold")
# Use custom title logic
final_title = self._get_final_title(title or "Gate Type Distribution")
if final_title is not None:
ax.set_title(final_title)
return self._finalize_plot(
fig=fig,
is_override=is_override,
save=save,
show=show,
filename="qiskit_gate_distribution",
)
[docs]
def plot_instruction_metrics(
self,
save: bool = False,
show: bool = False,
fig_ax_override: Optional[tuple[plt.Figure, plt.Axes]] = None,
title: Optional[str] = None,
) -> plt.Figure:
"""Plot instruction-related metrics."""
fig, ax, is_override = self._setup_plot_axes(fig_ax_override)
# Get instruction metrics using the base class utility
instruction_cols = [
"instruction_metrics.num_connected_components",
"instruction_metrics.num_nonlocal_gates",
"instruction_metrics.num_tensor_factors",
"instruction_metrics.num_unitary_factors",
]
instruction_data = self._extract_metrics_from_columns(
self.qiskit_df, instruction_cols, prefix_to_remove="instruction_metrics."
)
if not instruction_data:
self._show_no_data_message(
ax, title or "Instruction Metrics", "No instruction metrics available"
)
return self._finalize_plot(
fig=fig,
is_override=is_override,
save=save,
show=show,
filename="qiskit_instruction_metrics",
)
self._create_bar_plot_with_labels(
data=instruction_data,
ax=ax,
title=title or "Instruction Metrics",
xlabel="Metrics",
ylabel="Count",
value_format="int",
)
return self._finalize_plot(
fig=fig,
is_override=is_override,
save=save,
show=show,
filename="qiskit_instruction_metrics",
)
[docs]
def plot_circuit_summary(
self,
save: bool = False,
show: bool = False,
fig_ax_override: Optional[tuple[plt.Figure, plt.Axes]] = None,
title: Optional[str] = None,
) -> plt.Figure:
"""Plot a summary view of key circuit characteristics."""
fig, ax, is_override = self._setup_plot_axes(fig_ax_override)
# Calculate derived metrics
depth = self.qiskit_df["basic_metrics.depth"].iloc[0]
width = self.qiskit_df["basic_metrics.width"].iloc[0]
size = self.qiskit_df["basic_metrics.size"].iloc[0]
# Circuit density (gates per qubit-time unit)
circuit_volume = depth * width if depth > 0 and width > 0 else 1
gate_density = size / circuit_volume
# Parallelism factor (average gates per time step)
parallelism = size / depth if depth > 0 else 0
# Circuit efficiency metrics
summary_data = {
"Gate Density": round(gate_density, 3),
"Parallelism": round(parallelism, 3),
"Depth/Width Ratio": round(depth / width if width > 0 else 0, 3),
"Circuit Volume": circuit_volume,
}
self._create_bar_plot_with_labels(
data=summary_data,
ax=ax,
title=title or "Circuit Summary Metrics",
xlabel="Metrics",
ylabel="Value",
value_format="{:.3f}",
)
return self._finalize_plot(
fig=fig,
is_override=is_override,
save=save,
show=show,
filename="qiskit_circuit_summary",
)
[docs]
def create_dashboard(
self, save: bool = False, show: bool = False, title: Optional[str] = None
) -> plt.Figure:
"""Creates a comprehensive dashboard with all QiskitMetrics plots."""
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))
# Use custom title logic for dashboard
final_title = self._get_final_title(title or "QiskitMetrics Analysis Dashboard")
if final_title is not None:
fig.suptitle(final_title, fontsize=16)
# Create each plot on its designated axes
self.plot_circuit_structure(save=False, show=False, fig_ax_override=(fig, ax1))
self.plot_gate_distribution(save=False, show=False, fig_ax_override=(fig, ax2))
self.plot_instruction_metrics(save=False, show=False, fig_ax_override=(fig, ax3))
self.plot_circuit_summary(save=False, show=False, fig_ax_override=(fig, ax4))
plt.tight_layout()
if save:
self.save_plot(fig, "qiskit_metrics_dashboard")
if show:
self.show_plot(fig)
return fig