Skip to content
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

More plots for layer errors #1988

Merged
merged 28 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c524f55
swarm
SamFerracin Oct 21, 2024
df94994
fixes
SamFerracin Oct 21, 2024
e9cad6c
style
SamFerracin Oct 22, 2024
0486fb8
Merge branch 'main' of https://github.com/Qiskit/qiskit-ibm-runtime i…
SamFerracin Oct 22, 2024
148b37a
first tests
SamFerracin Oct 22, 2024
28386de
more tests
SamFerracin Oct 22, 2024
3c5aa85
all tests
SamFerracin Oct 22, 2024
b000565
done
SamFerracin Oct 22, 2024
e412117
Merge branch 'main' into more-plots
SamFerracin Oct 22, 2024
8f7b282
Merge branch 'main' of https://github.com/Qiskit/qiskit-ibm-runtime i…
SamFerracin Oct 27, 2024
254fa00
better swarm plots
SamFerracin Oct 28, 2024
d181a87
some improvements
SamFerracin Oct 29, 2024
e7e5ead
black
SamFerracin Oct 29, 2024
a1ec570
conflicts
SamFerracin Oct 30, 2024
1c1af48
tests
SamFerracin Oct 30, 2024
d18a314
improvements
SamFerracin Oct 31, 2024
33d3f46
removed bar plots
SamFerracin Oct 31, 2024
9afdbe9
black
SamFerracin Oct 31, 2024
7da10e3
Merge branch 'more-plots' of https://github.com/SamFerracin/qiskit-ib…
SamFerracin Oct 31, 2024
fd4d540
Merge branch 'main' of https://github.com/Qiskit/qiskit-ibm-runtime i…
SamFerracin Oct 31, 2024
847a31c
style
SamFerracin Oct 31, 2024
3093f2f
fixes
SamFerracin Oct 31, 2024
0ac9db4
Merge branch 'main' into more-plots
SamFerracin Nov 5, 2024
dd023a7
Merge branch 'main' into more-plots
SamFerracin Nov 5, 2024
64768b5
CR
SamFerracin Nov 5, 2024
dc2e326
Merge branch 'more-plots' of https://github.com/SamFerracin/qiskit-ib…
SamFerracin Nov 5, 2024
d314467
reno
SamFerracin Nov 5, 2024
94e5517
Update release-notes/unreleased/1988.feat.rst
SamFerracin Nov 5, 2024
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
5 changes: 3 additions & 2 deletions qiskit_ibm_runtime/execution_span/execution_spans.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,13 @@ def sort(self, inplace: bool = True) -> "ExecutionSpans":

def draw(
self, name: str = None, normalize_y: bool = False, line_width: int = 4
) -> "PlotlyFigure":
) -> PlotlyFigure:
"""Draw these execution spans.

.. note::
To draw multiple sets of execution spans at once, for example coming from multiple
jobs, consider calling :func:`~.draw_execution_spans` directly.
jobs, consider calling :meth:`~qiskit_ibm_runtime.visualization.draw_execution_spans`
directly.

Args:
name: The name of this set of spans.
Expand Down
92 changes: 79 additions & 13 deletions qiskit_ibm_runtime/utils/noise_learner_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

from qiskit.providers.backend import BackendV2
from qiskit.circuit import QuantumCircuit
from qiskit.quantum_info import PauliList
from qiskit.quantum_info import PauliList, Pauli

from ..utils.embeddings import Embedding
from ..utils.deprecation import issue_deprecation_msg
Expand Down Expand Up @@ -286,18 +286,84 @@ def draw_map(
from ..visualization import draw_layer_error_map

return draw_layer_error_map(
self,
embedding,
colorscale,
color_no_data,
color_out_of_scale,
num_edge_segments,
edge_width,
height,
highest_rate,
background_color,
radius,
width,
layer_error=self,
embedding=embedding,
colorscale=colorscale,
color_no_data=color_no_data,
color_out_of_scale=color_out_of_scale,
num_edge_segments=num_edge_segments,
edge_width=edge_width,
height=height,
highest_rate=highest_rate,
background_color=background_color,
radius=radius,
width=width,
)

def draw_swarm(
self,
num_bodies: Optional[int] = None,
max_rate: Optional[float] = None,
min_rate: Optional[float] = None,
connected: Optional[Union[list[Pauli], list[str]]] = None,
colors: Optional[list[str]] = None,
num_bins: Optional[int] = None,
opacities: Union[float, list[float]] = 0.4,
names: Optional[list[str]] = None,
x_coo: Optional[list[float]] = None,
marker_size: Optional[float] = None,
height: int = 500,
width: int = 800,
) -> PlotlyFigure:
r"""
Draw a swarm plot of the rates in this layer error.

This function plots the rates along a vertical axes, offsetting the rates along the ``x``
axis so that they do not overlap with each other.

.. note::
To draw multiple layer errors at once, consider calling
:meth:`~qiskit_ibm_runtime.visualization.draw_layer_errors_swarm` directly.

Args:
num_bodies: The weight of the generators to include in the plot, or ``None`` if all the
generators should be included.
max_rate: The largest rate to include in the plot, or ``None`` if no upper limit should be
set.
min_rate: The smallest rate to include in the plot, or ``None`` if no lower limit should be
set.
connected: A list of generators whose markers are to be connected by lines.
colors: A list of colors for the markers in the plot, or ``None`` if these colors are to be
chosen automatically.
num_bins: The number of bins to place the rates into when calculating the ``x``-axis
offsets.
opacities: A list of opacities for the markers.
names: The names of the various layers as displayed in the legend. If ``None``, default
names are assigned based on the layers' position inside the ``layer_errors`` list.
x_coo: The ``x``-axis coordinates of the vertical axes that the markers are drawn around, or
``None`` if these axes should be placed at regular intervals.
marker_size: The size of the marker in the plot.
height: The height of the returned figure.
width: The width of the returned figure.
"""
# pylint: disable=import-outside-toplevel, cyclic-import

from ..visualization import draw_layer_errors_swarm

return draw_layer_errors_swarm(
layer_errors=[self],
num_bodies=num_bodies,
max_rate=max_rate,
min_rate=min_rate,
connected=connected,
colors=colors,
num_bins=num_bins,
opacities=opacities,
names=names,
x_coo=x_coo,
marker_size=marker_size,
height=height,
width=width,
)

def _json(self) -> dict:
Expand Down
2 changes: 1 addition & 1 deletion qiskit_ibm_runtime/visualization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@
draw_layer_error_map
"""

from .draw_layer_error import draw_layer_error_map, draw_layer_errors_swarm
from .draw_execution_spans import draw_execution_spans
from .draw_layer_error_map import draw_layer_error_map
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
"""Functions to visualize :class:`~.NoiseLearnerResult` objects."""

from __future__ import annotations
from typing import Dict, Optional, Tuple, Union, TYPE_CHECKING
from typing import Any, Dict, Optional, Tuple, Union, TYPE_CHECKING

import numpy as np
from qiskit.providers.backend import BackendV2
from qiskit.quantum_info import Pauli

from ..utils.embeddings import Embedding
from ..utils.noise_learner_result import LayerError
Expand Down Expand Up @@ -62,7 +63,6 @@ def draw_layer_error_map(
Raises:
ValueError: If the given coordinates are incompatible with the specified backend.
ValueError: If ``backend`` has no coupling map.
ModuleNotFoundError: If the required ``plotly`` dependencies cannot be imported.
"""
go = plotly_module(".graph_objects")
sample_colorscale = plotly_module(".colors").sample_colorscale
Expand Down Expand Up @@ -262,3 +262,160 @@ def draw_layer_error_map(
fig.update_layout(plot_bgcolor=background_color)

return fig


def draw_layer_errors_swarm(
layer_errors: list[LayerError],
num_bodies: Optional[int] = None,
max_rate: Optional[float] = None,
min_rate: Optional[float] = None,
connected: Optional[list[Union[Pauli, str]]] = None,
colors: Optional[list[str]] = None,
num_bins: Optional[int] = None,
opacities: Union[float, list[float]] = 0.4,
names: Optional[list[str]] = None,
x_coo: Optional[list[float]] = None,
marker_size: Optional[float] = None,
height: int = 500,
width: int = 800,
) -> PlotlyFigure:
r"""
Draw a swarm plot for the given list of layer errors.

This function plots the rates of each of the given layer errors along a vertical axes,
offsetting the rates along the ``x`` axis to minimize the overlap between the markers. It helps
visualizing the distribution of errors for different layer errors, as well as to track (using
the ``connected`` argument) the evolution of specific generators across different layers.

.. note::

To calculate the offsets, this arranges the rates in ``num_bins`` equally-spaced bins, and
then it assigns the ``x`` coordinates so that all the rates in the same bins are spaced
around the vertical axis. Thus, a higher value of ``num_bins`` will result in higher
overlaps between the markers.

Args:
layer_errors: The layer errors to draw.
num_bodies: The weight of the generators to include in the plot, or ``None`` if all the
generators should be included.
max_rate: The largest rate to include in the plot, or ``None`` if no upper limit should be
set.
min_rate: The smallest rate to include in the plot, or ``None`` if no lower limit should be
set.
connected: A list of generators whose markers are to be connected by lines.
colors: A list of colors for the markers in the plot, or ``None`` if these colors are to be
chosen automatically.
num_bins: The number of bins to place the rates into when calculating the ``x``-axis
offsets.
opacities: A list of opacities for the markers.
names: The names of the various layers as displayed in the legend. If ``None``, default
names are assigned based on the layers' position inside the ``layer_errors`` list.
x_coo: The ``x``-axis coordinates of the vertical axes that the markers are drawn around, or
``None`` if these axes should be placed at regular intervals.
marker_size: The size of the marker in the plot.
height: The height of the returned figure.
width: The width of the returned figure.

Raises:
ValueError: If an invalid grouping option is given.
ValueError: If ``colors`` is given but its length is incorrect.
"""
go = plotly_module(".graph_objects")

colors = colors if colors else ["dodgerblue"] * len(layer_errors)
if len(colors) != len(layer_errors):
raise ValueError(f"Expected {len(layer_errors)} colors, found {len(colors)}.")

opacities = [opacities] * len(layer_errors) if isinstance(opacities, float) else opacities
if len(opacities) != len(layer_errors):
raise ValueError(f"Expected {len(layer_errors)} opacities, found {len(opacities)}.")

names = [f"layer #{i}" for i in range(len(layer_errors))] if not names else names
if len(names) != len(layer_errors):
raise ValueError(f"Expected {len(layer_errors)} names, found {len(names)}.")

x_coo = list(range(len(layer_errors))) if not x_coo else x_coo
if len(x_coo) != len(layer_errors):
raise ValueError(f"Expected {len(layer_errors)} ``x_coo``, found {len(x_coo)}.")

fig = go.Figure(layout=go.Layout(width=width, height=height))
fig.update_xaxes(
range=[x_coo[0] - 1, x_coo[-1] + 1],
showgrid=False,
zeroline=False,
title="layers",
)
fig.update_yaxes(title="rates")
fig.update_layout(xaxis={"tickvals": x_coo, "ticktext": names})

# Initialize a dictionary to store the coordinates of the generators that need to be connected
connected_d: dict[str, dict[str, list[float]]] = (
{str(p): {"xs": [], "ys": []} for p in connected} if connected else {}
)

for l_error_idx, l_error in enumerate(layer_errors):
error = l_error.error.restrict_num_bodies(num_bodies) if num_bodies else l_error.error
generators = error.generators.to_labels()
smallest_rate = min(rates := error.rates)
highest_rate = max(rates)

# Create bins
num_bins = num_bins or 10
bin_size = (highest_rate - smallest_rate) / num_bins
bins: dict[int, list[Any]] = {i: [] for i in range(num_bins + 1)}

# Populate the bins
for idx, (gen, rate) in enumerate(zip(generators, rates)):
if gen not in connected_d:
if (min_rate and rate < min_rate) or (max_rate and rate > max_rate):
continue
bins[int((rate - smallest_rate) // bin_size)] += [(gen, rate, gen in connected_d)]

# Assign `x` and `y` coordinates based on the bins
xs = []
ys = []
hoverinfo = []
for values in bins.values():
for idx, (gen, rate, is_connected) in enumerate(values):
xs.append(x := x_coo[l_error_idx] + (idx - len(values) // 2) / len(rates))
ys.append(rate)
hoverinfo.append(f"Generator: {gen}<br> rate: {rate}")

if is_connected:
connected_d[gen]["xs"].append(x)
connected_d[gen]["ys"].append(rate)

# Add the traces for the swarm plot of this layer error
fig.add_trace(
go.Scatter(
y=ys,
x=xs,
hovertemplate=hoverinfo,
mode="markers",
marker={
"color": colors[l_error_idx],
"opacity": opacities[l_error_idx],
"size": marker_size,
},
name=names[l_error_idx],
showlegend=False,
)
)

# Add the traces for the tracked errors
for gen, vals in connected_d.items():
hoverinfo = [
f"{name}<br> gen.: {gen}<br> rate: {y}" for name, y in zip(names, vals["ys"])
]

fig.add_trace(
go.Scatter(
y=vals["ys"],
x=vals["xs"],
mode="lines+markers",
name=str(gen),
hovertemplate=hoverinfo,
)
)

return fig
4 changes: 4 additions & 0 deletions release-notes/unreleased/1988.feat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Added :func:`~.draw_layer_errors_swarm` to draw a swarm plot of one or more
:class:`~.LayerError` objects. Also added the convenience method
:meth:`~.LayerError.draw_swarm` to invoke the drawing function on a particular instance.
(`1988 <https://github.com/Qiskit/qiskit-ibm-runtime/pull/1988>`__)
SamFerracin marked this conversation as resolved.
Show resolved Hide resolved
11 changes: 11 additions & 0 deletions test/unit/visualization/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,13 @@

from datetime import datetime, timedelta
import random
from types import ModuleType

import ddt

from qiskit_ibm_runtime.execution_span import ExecutionSpans, SliceSpan
from qiskit_ibm_runtime.visualization import draw_execution_spans
from qiskit_ibm_runtime.visualization.utils import plotly_module

from ..ibm_test_case import IBMTestCase


class TestUtils(IBMTestCase):
"""Tests for the utility module."""

def test_get_plotly_module(self):
"""Test that getting a module works."""
self.assertIsInstance(plotly_module(), ModuleType)
self.assertIsInstance(plotly_module(".graph_objects"), ModuleType)

def test_plotly_module_raises(self):
"""Test that correct error is raised."""
with self.assertRaisesRegex(
ModuleNotFoundError, "Install all qiskit-ibm-runtime visualization dependencies"
):
plotly_module(".not_a_module")
from ...ibm_test_case import IBMTestCase


@ddt.ddt
Expand Down
Loading