# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# 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.
# pylint: disable=invalid-name
"""Interactive error map for IBM Quantum devices."""
import math
from typing import Tuple, Union
import matplotlib as mpl
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from qiskit_ibm_provider.ibm_backend import IBMBackend
from .plotly_wrapper import PlotlyWidget, PlotlyFigure
from ..colormaps import HELIX_LIGHT, HELIX_LIGHT_CMAP, HELIX_DARK, HELIX_DARK_CMAP
from ..device_layouts import DEVICE_LAYOUTS
from ..exceptions import VisualizationValueError, VisualizationTypeError
[docs]def iplot_error_map(
backend: IBMBackend,
figsize: Tuple[int] = (800, 500),
show_title: bool = True,
remove_badcal_edges: bool = True,
background_color: str = "white",
as_widget: bool = False,
) -> Union[PlotlyFigure, PlotlyWidget]:
"""Plot the error map of a device.
Args:
backend: Plot the error map for this backend.
figsize: Figure size in pixels.
show_title: Whether to show figure title.
remove_badcal_edges: Whether to remove bad CX gate calibration data.
background_color: Background color, either 'white' or 'black'.
as_widget: ``True`` if the figure is to be returned as a ``PlotlyWidget``.
Otherwise the figure is to be returned as a ``PlotlyFigure``.
Returns:
The error map figure.
Raises:
VisualizationValueError: If an invalid input is received.
VisualizationTypeError: If the specified `backend` is a simulator.
Example:
.. jupyter-execute::
:hide-code:
:hide-output:
from qiskit_ibm_provider.test.ibm_provider_mock import mock_get_backend
mock_get_backend('FakeVigo')
.. jupyter-execute::
from qiskit_ibm_provider import IBMProvider
from qiskit_ibm_provider.visualization import iplot_error_map
provider = IBMProvider(group='open', project='main')
backend = provider.get_backend('ibmq_vigo')
iplot_error_map(backend, as_widget=True)
"""
meas_text_color = "#000000"
if background_color == "white":
color_map = HELIX_LIGHT_CMAP
text_color = "#000000"
plotly_cmap = HELIX_LIGHT
elif background_color == "black":
color_map = HELIX_DARK_CMAP
text_color = "#FFFFFF"
plotly_cmap = HELIX_DARK
else:
raise VisualizationValueError(
'"{}" is not a valid background_color selection.'.format(background_color)
)
if backend.configuration().simulator:
raise VisualizationTypeError("Requires a device backend, not a simulator.")
config = backend.configuration()
n_qubits = config.n_qubits
cmap = config.coupling_map
if n_qubits in DEVICE_LAYOUTS:
grid_data = DEVICE_LAYOUTS[n_qubits]
else:
fig = go.Figure()
fig.update_layout(
showlegend=False,
plot_bgcolor=background_color,
paper_bgcolor=background_color,
width=figsize[0],
height=figsize[1],
margin={"t": 60, "l": 0, "r": 0, "b": 0},
)
out = PlotlyWidget(fig)
return out
props = backend.properties().to_dict()
t1s = []
t2s = []
for qubit_props in props["qubits"]:
count = 0
for item in qubit_props:
if item["name"] == "T1":
t1s.append(item["value"])
count += 1
elif item["name"] == "T2":
t2s.append(item["value"])
count += 1
if count == 2:
break
# U2 error rates
single_gate_errors = [0] * n_qubits
for gate in props["gates"]:
if gate["gate"] == "u2":
_qubit = gate["qubits"][0]
single_gate_errors[_qubit] = gate["parameters"][0]["value"]
# Convert to percent
single_gate_errors = 100 * np.asarray(single_gate_errors)
avg_1q_err = np.mean(single_gate_errors)
max_1q_err = max(single_gate_errors)
single_norm = mpl.colors.Normalize(vmin=min(single_gate_errors), vmax=max_1q_err)
q_colors = [
mpl.colors.rgb2hex(color_map(single_norm(err))) for err in single_gate_errors
]
line_colors = []
cx_idx = []
if n_qubits > 1 and cmap:
cx_errors = []
for cmap_qubits in cmap:
for gate in props["gates"]:
if gate["qubits"] == cmap_qubits:
cx_errors.append(gate["parameters"][0]["value"])
break
else:
continue
# Convert to percent
cx_errors = 100 * np.asarray(cx_errors)
# remove bad cx edges
if remove_badcal_edges:
cx_idx = np.where(cx_errors != 100.0)[0]
else:
cx_idx = np.arange(len(cx_errors))
avg_cx_err = np.mean(cx_errors[cx_idx])
for err in cx_errors:
if err != 100.0 or not remove_badcal_edges:
cx_norm = mpl.colors.Normalize(
vmin=min(cx_errors[cx_idx]), vmax=max(cx_errors[cx_idx])
)
line_colors.append(mpl.colors.rgb2hex(color_map(cx_norm(err))))
else:
line_colors.append("#ff0000")
# Measurement errors
read_err = []
for qubit in range(n_qubits):
for item in props["qubits"][qubit]:
if item["name"] == "readout_error":
read_err.append(item["value"])
read_err = 100 * np.asarray(read_err)
avg_read_err = np.mean(read_err)
max_read_err = np.max(read_err)
if n_qubits < 10:
num_left = n_qubits
num_right = 0
else:
num_left = math.ceil(n_qubits / 2)
num_right = n_qubits - num_left
x_max = max(d[1] for d in grid_data)
y_max = max(d[0] for d in grid_data)
max_dim = max(x_max, y_max)
qubit_size = 32
font_size = 14
offset = 0
if cmap:
if y_max / max_dim < 0.33:
qubit_size = 24
font_size = 10
offset = 1
if n_qubits > 5:
right_meas_title = "Readout Error (%)"
else:
right_meas_title = None
if cmap and cx_idx.size > 0:
cx_title = "CNOT Error Rate [Avg. {}%]".format(np.round(avg_cx_err, 3))
else:
cx_title = None
fig = make_subplots(
rows=2,
cols=11,
row_heights=[0.95, 0.05],
vertical_spacing=0.15,
specs=[
[
{"colspan": 2},
None,
{"colspan": 6},
None,
None,
None,
None,
None,
{"colspan": 2},
None,
None,
],
[
{"colspan": 4},
None,
None,
None,
None,
None,
{"colspan": 4},
None,
None,
None,
None,
],
],
subplot_titles=(
"Readout Error (%)",
None,
right_meas_title,
"Hadamard Error Rate [Avg. {}%]".format(np.round(avg_1q_err, 3)),
cx_title,
),
)
# Add lines for couplings
if cmap and n_qubits > 1 and cx_idx.size > 0:
for ind, edge in enumerate(cmap):
is_symmetric = False
if edge[::-1] in cmap:
is_symmetric = True
y_start = grid_data[edge[0]][0] + offset
x_start = grid_data[edge[0]][1]
y_end = grid_data[edge[1]][0] + offset
x_end = grid_data[edge[1]][1]
if is_symmetric:
if y_start == y_end:
x_end = (x_end - x_start) / 2 + x_start
x_mid = x_end
y_mid = y_start
elif x_start == x_end:
y_end = (y_end - y_start) / 2 + y_start
x_mid = x_start
y_mid = y_end
else:
x_end = (x_end - x_start) / 2 + x_start
y_end = (y_end - y_start) / 2 + y_start
x_mid = x_end
y_mid = y_end
else:
if y_start == y_end:
x_mid = (x_end - x_start) / 2 + x_start
y_mid = y_end
elif x_start == x_end:
x_mid = x_end
y_mid = (y_end - y_start) / 2 + y_start
else:
x_mid = (x_end - x_start) / 2 + x_start
y_mid = (y_end - y_start) / 2 + y_start
fig.add_trace(
go.Scatter(
x=[x_start, x_mid, x_end],
y=[-y_start, -y_mid, -y_end],
mode="lines",
line={"width": 6, "color": line_colors[ind]},
hoverinfo="text",
hovertext="CX<sub>err</sub>{B}_{A} = {err} %".format(
A=edge[0], B=edge[1], err=np.round(cx_errors[ind], 3)
),
),
row=1,
col=3,
)
# Add the qubits themselves
qubit_text = []
qubit_str = "<b>Qubit {}</b><br>H<sub>err</sub> = {} %"
qubit_str += "<br>T1 = {} \u03BCs<br>T2 = {} \u03BCs"
for kk in range(n_qubits):
qubit_text.append(
qubit_str.format(
kk,
np.round(single_gate_errors[kk], 3),
np.round(t1s[kk], 2),
np.round(t2s[kk], 2),
)
)
if n_qubits > 20:
qubit_size = 23
font_size = 11
if n_qubits > 50:
qubit_size = 20
font_size = 9
qtext_color = []
for ii in range(n_qubits):
if background_color == "black":
if single_gate_errors[ii] > 0.8 * max_1q_err:
qtext_color.append("black")
else:
qtext_color.append("white")
else:
qtext_color.append("white")
fig.add_trace(
go.Scatter(
x=[d[1] for d in grid_data],
y=[-d[0] - offset for d in grid_data],
mode="markers+text",
marker=go.scatter.Marker(size=qubit_size, color=q_colors, opacity=1),
text=[str(ii) for ii in range(n_qubits)],
textposition="middle center",
textfont={"size": font_size, "color": qtext_color},
hoverinfo="text",
hovertext=qubit_text,
),
row=1,
col=3,
)
fig.update_xaxes(row=1, col=3, visible=False)
_range = None
if offset:
_range = [-3.5, 0.5]
fig.update_yaxes(row=1, col=3, visible=False, range=_range)
# H error rate colorbar
min_1q_err = min(single_gate_errors)
max_1q_err = max(single_gate_errors)
if n_qubits > 1:
fig.add_trace(
go.Heatmap(
z=[
np.linspace(min_1q_err, max_1q_err, 100),
np.linspace(min_1q_err, max_1q_err, 100),
],
colorscale=plotly_cmap,
showscale=False,
hoverinfo="none",
),
row=2,
col=1,
)
fig.update_yaxes(row=2, col=1, visible=False)
fig.update_xaxes(
row=2,
col=1,
tickvals=[0, 49, 99],
ticktext=[
np.round(min_1q_err, 3),
np.round((max_1q_err - min_1q_err) / 2 + min_1q_err, 3),
np.round(max_1q_err, 3),
],
)
# CX error rate colorbar
if cmap and n_qubits > 1 and cx_idx.size > 0:
min_cx_err = min(cx_errors)
max_cx_err = max(cx_errors)
if min_cx_err == max_cx_err:
min_cx_err = 0 # Force more than 1 color.
fig.add_trace(
go.Heatmap(
z=[
np.linspace(min_cx_err, max_cx_err, 100),
np.linspace(min_cx_err, max_cx_err, 100),
],
colorscale=plotly_cmap,
showscale=False,
hoverinfo="none",
),
row=2,
col=7,
)
fig.update_yaxes(row=2, col=7, visible=False)
min_cx_idx_err = min(cx_errors[cx_idx])
max_cx_idx_err = max(cx_errors[cx_idx])
fig.update_xaxes(
row=2,
col=7,
tickvals=[0, 49, 99],
ticktext=[
np.round(min_cx_idx_err, 3),
np.round((max_cx_idx_err - min_cx_idx_err) / 2 + min_cx_idx_err, 3),
np.round(max_cx_idx_err, 3),
],
)
hover_text = "<b>Qubit {}</b><br>M<sub>err</sub> = {} %"
# Add the left side meas errors
for kk in range(num_left - 1, -1, -1):
fig.add_trace(
go.Bar(
x=[read_err[kk]],
y=[kk],
orientation="h",
marker={"color": "#eedccb"},
hoverinfo="text",
hoverlabel={"font": {"color": meas_text_color}},
hovertext=[hover_text.format(kk, np.round(read_err[kk], 3))],
),
row=1,
col=1,
)
fig.add_trace(
go.Scatter(
x=[avg_read_err, avg_read_err],
y=[-0.25, num_left - 1 + 0.25],
mode="lines",
hoverinfo="none",
line={"color": text_color, "width": 2, "dash": "dot"},
),
row=1,
col=1,
)
fig.update_yaxes(row=1, col=1, tickvals=list(range(num_left)), autorange="reversed")
fig.update_xaxes(
row=1,
col=1,
range=[0, 1.1 * max_read_err],
tickvals=[0, np.round(avg_read_err, 2), np.round(max_read_err, 2)],
showline=True,
linewidth=1,
linecolor=text_color,
tickcolor=text_color,
ticks="outside",
showgrid=False,
zeroline=False,
)
# Add the right side meas errors, if any
if num_right:
for kk in range(n_qubits - 1, num_left - 1, -1):
fig.add_trace(
go.Bar(
x=[-read_err[kk]],
y=[kk],
orientation="h",
marker={"color": "#eedccb"},
hoverinfo="text",
hoverlabel={"font": {"color": meas_text_color}},
hovertext=[hover_text.format(kk, np.round(read_err[kk], 3))],
),
row=1,
col=9,
)
fig.add_trace(
go.Scatter(
x=[-avg_read_err, -avg_read_err],
y=[num_left - 0.25, n_qubits - 1 + 0.25],
mode="lines",
hoverinfo="none",
line={"color": text_color, "width": 2, "dash": "dot"},
),
row=1,
col=9,
)
fig.update_yaxes(
row=1,
col=9,
tickvals=list(range(n_qubits - 1, num_left - 1, -1)),
side="right",
autorange="reversed",
)
fig.update_xaxes(
row=1,
col=9,
range=[-1.1 * max_read_err, 0],
tickvals=[0, -np.round(avg_read_err, 2), -np.round(max_read_err, 2)],
ticktext=[0, np.round(avg_read_err, 2), np.round(max_read_err, 2)],
showline=True,
linewidth=1,
linecolor=text_color,
tickcolor=text_color,
ticks="outside",
showgrid=False,
zeroline=False,
)
# Makes the subplot titles smaller than the 16pt default
for ann in fig["layout"]["annotations"]:
ann["font"] = {"size": 13}
title_text = "{} Error Map".format(backend.name) if show_title else ""
fig.update_layout(
showlegend=False,
plot_bgcolor=background_color,
paper_bgcolor=background_color,
width=figsize[0],
height=figsize[1],
title={"text": title_text, "x": 0.452},
title_font_size=20,
font={"color": text_color},
margin={"t": 60, "l": 0, "r": 40, "b": 0},
)
if as_widget:
return PlotlyWidget(fig)
return PlotlyFigure(fig)