Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
# 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


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)