# -*- coding: utf-8 -*-

# Copyright 2018, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

# pylint: disable=invalid-name,anomalous-backslash-in-string,ungrouped-imports,import-error

"""
Visualization functions for quantum states.
"""

import warnings
from functools import reduce
import numpy as np
from scipy import linalg
from qiskit.quantum_info.operators.pauli import pauli_group, Pauli
from .matplotlib import HAS_MATPLOTLIB

if HAS_MATPLOTLIB:
    from matplotlib.ticker import MaxNLocator
    from matplotlib import pyplot as plt
    from matplotlib.patches import FancyArrowPatch
    import matplotlib.colors as mcolors
    from matplotlib.colors import Normalize, LightSource
    from mpl_toolkits.mplot3d import proj3d
    from mpl_toolkits.mplot3d.art3d import Poly3DCollection
    from qiskit.visualization.exceptions import VisualizationError
    from qiskit.visualization.bloch import Bloch
    from qiskit.visualization.utils import _validate_input_state

if HAS_MATPLOTLIB:
    class Arrow3D(FancyArrowPatch):
        """Standard 3D arrow."""

        def __init__(self, xs, ys, zs, *args, **kwargs):
            """Create arrow."""
            FancyArrowPatch.__init__(self, (0, 0), (0, 0), *args, **kwargs)
            self._verts3d = xs, ys, zs

        def draw(self, renderer):
            """Draw the arrow."""
            xs3d, ys3d, zs3d = self._verts3d
            xs, ys, _ = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
            self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
            FancyArrowPatch.draw(self, renderer)

def plot_state_hinton(rho, title='', figsize=None):
    """Plot a hinton diagram for the quanum state.

    Args:
        rho (ndarray): Numpy array for state vector or density matrix.
        title (str): a string that represents the plot title
        figsize (tuple): Figure size in inches.
    Returns:
         matplotlib.Figure: The matplotlib.Figure of the visualization

    Raises:
        ImportError: Requires matplotlib.
    """
    if not HAS_MATPLOTLIB:
        raise ImportError('Must have Matplotlib installed.')
    rho = _validate_input_state(rho)
    if figsize is None:
        figsize = (8, 5)
    num = int(np.log2(len(rho)))
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize)
    max_weight = 2 ** np.ceil(np.log(np.abs(rho).max()) / np.log(2))
    datareal = np.real(rho)
    dataimag = np.imag(rho)
    column_names = [bin(i)[2:].zfill(num) for i in range(2**num)]
    row_names = [bin(i)[2:].zfill(num) for i in range(2**num)]
    lx = len(datareal[0])            # Work out matrix dimensions
    ly = len(datareal[:, 0])
    # Real
    ax1.patch.set_facecolor('gray')
    ax1.set_aspect('equal', 'box')
    ax1.xaxis.set_major_locator(plt.NullLocator())
    ax1.yaxis.set_major_locator(plt.NullLocator())

    for (x, y), w in np.ndenumerate(datareal):
        color = 'white' if w > 0 else 'black'
        size = np.sqrt(np.abs(w) / max_weight)
        rect = plt.Rectangle([x - size / 2, y - size / 2], size, size,
                             facecolor=color, edgecolor=color)
        ax1.add_patch(rect)

    ax1.set_xticks(np.arange(0, lx+0.5, 1))
    ax1.set_yticks(np.arange(0, ly+0.5, 1))
    ax1.set_yticklabels(row_names, fontsize=14)
    ax1.set_xticklabels(column_names, fontsize=14, rotation=90)
    ax1.autoscale_view()
    ax1.invert_yaxis()
    ax1.set_title('Real[rho]', fontsize=14)
    # Imaginary
    ax2.patch.set_facecolor('gray')
    ax2.set_aspect('equal', 'box')
    ax2.xaxis.set_major_locator(plt.NullLocator())
    ax2.yaxis.set_major_locator(plt.NullLocator())

    for (x, y), w in np.ndenumerate(dataimag):
        color = 'white' if w > 0 else 'black'
        size = np.sqrt(np.abs(w) / max_weight)
        rect = plt.Rectangle([x - size / 2, y - size / 2], size, size,
                             facecolor=color, edgecolor=color)
        ax2.add_patch(rect)
    if np.any(dataimag != 0):
        ax2.set_xticks(np.arange(0, lx+0.5, 1))
        ax2.set_yticks(np.arange(0, ly+0.5, 1))
        ax2.set_yticklabels(row_names, fontsize=14)
        ax2.set_xticklabels(column_names, fontsize=14, rotation=90)
    ax2.autoscale_view()
    ax2.invert_yaxis()
    ax2.set_title('Imag[rho]', fontsize=14)
    if title:
        fig.suptitle(title, fontsize=16)
    plt.tight_layout()
    plt.close(fig)
    return fig

def plot_bloch_vector(bloch, title="", ax=None, figsize=None):
    """Plot the Bloch sphere.

    Plot a sphere, axes, the Bloch vector, and its projections onto each axis.

    Args:
        bloch (list[double]): array of three elements where [<x>, <y>, <z>]
        title (str): a string that represents the plot title
        ax (matplotlib.Axes): An Axes to use for rendering the bloch sphere
        figsize (tuple): Figure size in inches. Has no effect is passing `ax`.

    Returns:
        Figure: A matplotlib figure instance if `ax = None`.

    Raises:
        ImportError: Requires matplotlib.
    """
    if not HAS_MATPLOTLIB:
        raise ImportError('Must have Matplotlib installed.')
    if figsize is None:
        figsize = (5, 5)
    B = Bloch(axes=ax)
    B.add_vectors(bloch)
    B.render(title=title)
    if ax is None:
        fig = B.fig
        fig.set_size_inches(figsize[0], figsize[1])
        plt.close(fig)
        return fig
    return None

def plot_bloch_multivector(rho, title='', figsize=None):
    """Plot the Bloch sphere.

    Plot a sphere, axes, the Bloch vector, and its projections onto each axis.

    Args:
        rho (ndarray): Numpy array for state vector or density matrix.
        title (str): a string that represents the plot title
        figsize (tuple): Has no effect, here for compatibility only.

    Returns:
        Figure: A matplotlib figure instance if `ax = None`.

    Raises:
        ImportError: Requires matplotlib.
    """
    if not HAS_MATPLOTLIB:
        raise ImportError('Must have Matplotlib installed.')
    rho = _validate_input_state(rho)
    num = int(np.log2(len(rho)))
    width, height = plt.figaspect(1/num)
    fig = plt.figure(figsize=(width, height))
    for i in range(num):
        ax = fig.add_subplot(1, num, i + 1, projection='3d')
        pauli_singles = [
            Pauli.pauli_single(num, i, 'X'),
            Pauli.pauli_single(num, i, 'Y'),
            Pauli.pauli_single(num, i, 'Z')
        ]
        bloch_state = list(
            map(lambda x: np.real(np.trace(np.dot(x.to_matrix(), rho))),
                pauli_singles))
        plot_bloch_vector(bloch_state, "qubit " + str(i), ax=ax,
                          figsize=figsize)
    fig.suptitle(title, fontsize=16)
    plt.close(fig)
    return fig

def plot_state_city(rho, title="", figsize=None, color=None,
                    alpha=1):
    """Plot the cityscape of quantum state.

    Plot two 3d bar graphs (two dimensional) of the real and imaginary
    part of the density matrix rho.

    Args:
        rho (ndarray): Numpy array for state vector or density matrix.
        title (str): a string that represents the plot title
        figsize (tuple): Figure size in inches.
        color (list): A list of len=2 giving colors for real and
        imaginary components of matrix elements.
        alpha (float): Transparency value for bars
    Returns:
         matplotlib.Figure: The matplotlib.Figure of the visualization

    Raises:
        ImportError: Requires matplotlib.
        ValueError: When 'color' is not a list of len=2.
    """
    if not HAS_MATPLOTLIB:
        raise ImportError('Must have Matplotlib installed.')
    rho = _validate_input_state(rho)

    num = int(np.log2(len(rho)))
    # get the real and imag parts of rho
    datareal = np.real(rho)
    dataimag = np.imag(rho)

    # get the labels
    column_names = [bin(i)[2:].zfill(num) for i in range(2**num)]
    row_names = [bin(i)[2:].zfill(num) for i in range(2**num)]

    lx = len(datareal[0])            # Work out matrix dimensions
    ly = len(datareal[:, 0])
    xpos = np.arange(0, lx, 1)    # Set up a mesh of positions
    ypos = np.arange(0, ly, 1)
    xpos, ypos = np.meshgrid(xpos+0.25, ypos+0.25)

    xpos = xpos.flatten()
    ypos = ypos.flatten()
    zpos = np.zeros(lx*ly)

    dx = 0.5 * np.ones_like(zpos)  # width of bars
    dy = dx.copy()
    dzr = datareal.flatten()
    dzi = dataimag.flatten()

    if color is None:
        color = ["#648fff", "#648fff"]
    else:
        if len(color) != 2:
            raise ValueError("'color' must be a list of len=2.")
        if color[0] is None:
            color[0] = "#648fff"
        if color[1] is None:
            color[1] = "#648fff"

    # set default figure size
    if figsize is None:
        figsize = (15, 5)

    fig = plt.figure(figsize=figsize)
    ax1 = fig.add_subplot(1, 2, 1, projection='3d')

    x = [0, max(xpos)+0.5, max(xpos)+0.5, 0]
    y = [0, 0, max(ypos)+0.5, max(ypos)+0.5]
    z = [0, 0, 0, 0]
    verts = [list(zip(x, y, z))]

    fc1 = generate_facecolors(xpos, ypos, zpos, dx, dy, dzr, color[0])
    for idx, cur_zpos in enumerate(zpos):
        if dzr[idx] > 0:
            zorder = 2
        else:
            zorder = 0
        b1 = ax1.bar3d(xpos[idx], ypos[idx], cur_zpos,
                       dx[idx], dy[idx], dzr[idx],
                       alpha=alpha, zorder=zorder)
        b1.set_facecolors(fc1[6*idx:6*idx+6])

    pc1 = Poly3DCollection(verts, alpha=0.15, facecolor='k',
                           linewidths=1, zorder=1)

    if min(dzr) < 0 < max(dzr):
        ax1.add_collection3d(pc1)

    ax2 = fig.add_subplot(1, 2, 2, projection='3d')
    fc2 = generate_facecolors(xpos, ypos, zpos, dx, dy, dzi, color[1])
    for idx, cur_zpos in enumerate(zpos):
        if dzi[idx] > 0:
            zorder = 2
        else:
            zorder = 0
        b2 = ax2.bar3d(xpos[idx], ypos[idx], cur_zpos,
                       dx[idx], dy[idx], dzi[idx],
                       alpha=alpha, zorder=zorder)
        b2.set_facecolors(fc2[6*idx:6*idx+6])

    pc2 = Poly3DCollection(verts, alpha=0.2, facecolor='k',
                           linewidths=1, zorder=1)

    if min(dzi) < 0 < max(dzi):
        ax2.add_collection3d(pc2)
    ax1.set_xticks(np.arange(0.5, lx+0.5, 1))
    ax1.set_yticks(np.arange(0.5, ly+0.5, 1))
    max_dzr = max(dzr)
    min_dzr = min(dzr)
    if max_dzr != min_dzr:
        ax1.axes.set_zlim3d(np.min(dzr), np.max(dzr)+1e-9)
    else:
        if min_dzr == 0:
            ax1.axes.set_zlim3d(np.min(dzr), np.max(dzr)+1e-9)
        else:
            ax1.axes.set_zlim3d(auto=True)
    ax1.zaxis.set_major_locator(MaxNLocator(5))
    ax1.w_xaxis.set_ticklabels(row_names, fontsize=14, rotation=45)
    ax1.w_yaxis.set_ticklabels(column_names, fontsize=14, rotation=-22.5)
    ax1.set_zlabel("Real[rho]", fontsize=14)
    for tick in ax1.zaxis.get_major_ticks():
        tick.label.set_fontsize(14)

    ax2.set_xticks(np.arange(0.5, lx+0.5, 1))
    ax2.set_yticks(np.arange(0.5, ly+0.5, 1))
    min_dzi = np.min(dzi)
    max_dzi = np.max(dzi)
    if min_dzi != max_dzi:
        eps = 0
        ax2.zaxis.set_major_locator(MaxNLocator(5))
        ax2.axes.set_zlim3d(np.min(dzi), np.max(dzi)+eps)
    else:
        if min_dzi == 0:
            ax2.set_zticks([0])
            eps = 1e-9
            ax2.axes.set_zlim3d(np.min(dzi), np.max(dzi)+eps)
        else:
            ax2.axes.set_zlim3d(auto=True)
    ax2.w_xaxis.set_ticklabels(row_names, fontsize=14, rotation=45)
    ax2.w_yaxis.set_ticklabels(column_names, fontsize=14, rotation=-22.5)
    ax2.set_zlabel("Imag[rho]", fontsize=14)
    for tick in ax2.zaxis.get_major_ticks():
        tick.label.set_fontsize(14)
    plt.suptitle(title, fontsize=16)
    plt.tight_layout()
    plt.close(fig)
    return fig

def plot_state_paulivec(rho, title="", figsize=None, color=None):
    """Plot the paulivec representation of a quantum state.

    Plot a bargraph of the mixed state rho over the pauli matrices

    Args:
        rho (ndarray): Numpy array for state vector or density matrix
        title (str): a string that represents the plot title
        figsize (tuple): Figure size in inches.
        color (list or str): Color of the expectation value bars.
    Returns:
         matplotlib.Figure: The matplotlib.Figure of the visualization
    Raises:
        ImportError: Requires matplotlib.
    """
    if not HAS_MATPLOTLIB:
        raise ImportError('Must have Matplotlib installed.')
    rho = _validate_input_state(rho)
    if figsize is None:
        figsize = (7, 5)
    num = int(np.log2(len(rho)))
    labels = list(map(lambda x: x.to_label(), pauli_group(num)))
    values = list(map(lambda x: np.real(np.trace(np.dot(x.to_matrix(), rho))),
                      pauli_group(num)))
    numelem = len(values)
    if color is None:
        color = "#648fff"

    ind = np.arange(numelem)  # the x locations for the groups
    width = 0.5  # the width of the bars
    fig, ax = plt.subplots(figsize=figsize)
    ax.grid(zorder=0, linewidth=1, linestyle='--')
    ax.bar(ind, values, width, color=color, zorder=2)
    ax.axhline(linewidth=1, color='k')
    # add some text for labels, title, and axes ticks
    ax.set_ylabel('Expectation value', fontsize=14)
    ax.set_xticks(ind)
    ax.set_yticks([-1, -0.5, 0, 0.5, 1])
    ax.set_xticklabels(labels, fontsize=14, rotation=70)
    ax.set_xlabel('Pauli', fontsize=14)
    ax.set_ylim([-1, 1])
    ax.set_facecolor('#eeeeee')
    for tick in ax.xaxis.get_major_ticks()+ax.yaxis.get_major_ticks():
        tick.label.set_fontsize(14)
    ax.set_title(title, fontsize=16)
    plt.close(fig)
    return fig

def n_choose_k(n, k):
    """Return the number of combinations for n choose k.

    Args:
        n (int): the total number of options .
        k (int): The number of elements.

    Returns:
        int: returns the binomial coefficient
    """
    if n == 0:
        return 0
    return reduce(lambda x, y: x * y[0] / y[1],
                  zip(range(n - k + 1, n + 1),
                      range(1, k + 1)), 1)

def lex_index(n, k, lst):
    """Return  the lex index of a combination..

    Args:
        n (int): the total number of options .
        k (int): The number of elements.
        lst (list): list

    Returns:
        int: returns int index for lex order

    Raises:
        VisualizationError: if length of list is not equal to k
    """
    if len(lst) != k:
        raise VisualizationError("list should have length k")
    comb = list(map(lambda x: n - 1 - x, lst))
    dualm = sum([n_choose_k(comb[k - 1 - i], i + 1) for i in range(k)])
    return int(dualm)

def bit_string_index(s):
    """Return the index of a string of 0s and 1s."""
    n = len(s)
    k = s.count("1")
    if s.count("0") != n - k:
        raise VisualizationError("s must be a string of 0 and 1")
    ones = [pos for pos, char in enumerate(s) if char == "1"]
    return lex_index(n, k, ones)

def phase_to_color_wheel(complex_number):
    """Map a phase of a complexnumber to a color in (r,g,b).

    complex_number is phase is first mapped to angle in the range
    [0, 2pi] and then to a color wheel with blue at zero phase.
    """
    angles = np.angle(complex_number)
    angle_round = int(((angles + 2 * np.pi) % (2 * np.pi))/np.pi*6)
    color_map = {
        0: (0, 0, 1),  # blue,
        1: (0.5, 0, 1),  # blue-violet
        2: (1, 0, 1),  # violet
        3: (1, 0, 0.5),  # red-violet,
        4: (1, 0, 0),  # red
        5: (1, 0.5, 0),  # red-oranage,
        6: (1, 1, 0),  # orange
        7: (0.5, 1, 0),  # orange-yellow
        8: (0, 1, 0),  # yellow,
        9: (0, 1, 0.5),  # yellow-green,
        10: (0, 1, 1),  # green,
        11: (0, 0.5, 1)  # green-blue,
    }
    return color_map[angle_round]

def plot_state_qsphere(rho, figsize=None):
    """Plot the qsphere representation of a quantum state.

    Args:
        rho (ndarray): State vector or density matrix representation.
        of quantum state.
        figsize (tuple): Figure size in inches.

    Returns:
        Figure: A matplotlib figure instance.

    Raises:
        ImportError: Requires matplotlib.
    """
    if not HAS_MATPLOTLIB:
        raise ImportError('Must have Matplotlib installed.')
    rho = _validate_input_state(rho)
    if figsize is None:
        figsize = (7, 7)
    num = int(np.log2(len(rho)))
    # get the eigenvectors and eigenvalues
    we, stateall = linalg.eigh(rho)
    for _ in range(2**num):
        # start with the max
        probmix = we.max()
        prob_location = we.argmax()
        if probmix > 0.001:
            # get the max eigenvalue
            state = stateall[:, prob_location]
            loc = np.absolute(state).argmax()
            # get the element location closes to lowest bin representation.
            for j in range(2**num):
                test = np.absolute(np.absolute(state[j]) -
                                   np.absolute(state[loc]))
                if test < 0.001:
                    loc = j
                    break
            # remove the global phase
            angles = (np.angle(state[loc]) + 2 * np.pi) % (2 * np.pi)
            angleset = np.exp(-1j*angles)
            # print(state)
            # print(angles)
            state = angleset*state
            # print(state)
            state.flatten()
            # start the plotting
            fig = plt.figure(figsize=figsize)
            ax = fig.add_subplot(111, projection='3d')
            ax.axes.set_xlim3d(-1.0, 1.0)
            ax.axes.set_ylim3d(-1.0, 1.0)
            ax.axes.set_zlim3d(-1.0, 1.0)
            ax.set_aspect("equal")
            ax.axes.grid(False)
            # Plot semi-transparent sphere
            u = np.linspace(0, 2 * np.pi, 25)
            v = np.linspace(0, np.pi, 25)
            x = np.outer(np.cos(u), np.sin(v))
            y = np.outer(np.sin(u), np.sin(v))
            z = np.outer(np.ones(np.size(u)), np.cos(v))
            ax.plot_surface(x, y, z, rstride=1, cstride=1, color='k',
                            alpha=0.05, linewidth=0)
            # wireframe
            # Get rid of the panes
            ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
            ax.w_yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
            ax.w_zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))

            # Get rid of the spines
            ax.w_xaxis.line.set_color((1.0, 1.0, 1.0, 0.0))
            ax.w_yaxis.line.set_color((1.0, 1.0, 1.0, 0.0))
            ax.w_zaxis.line.set_color((1.0, 1.0, 1.0, 0.0))
            # Get rid of the ticks
            ax.set_xticks([])
            ax.set_yticks([])
            ax.set_zticks([])

            d = num
            for i in range(2**num):
                # get x,y,z points
                element = bin(i)[2:].zfill(num)
                weight = element.count("1")
                zvalue = -2 * weight / d + 1
                number_of_divisions = n_choose_k(d, weight)
                weight_order = bit_string_index(element)
                # if weight_order >= number_of_divisions / 2:
                #    com_key = compliment(element)
                #    weight_order_temp = bit_string_index(com_key)
                #    weight_order = np.floor(
                #        number_of_divisions / 2) + weight_order_temp + 1
                angle = weight_order * 2 * np.pi / number_of_divisions
                xvalue = np.sqrt(1 - zvalue**2) * np.cos(angle)
                yvalue = np.sqrt(1 - zvalue**2) * np.sin(angle)
                ax.plot([xvalue], [yvalue], [zvalue],
                        markerfacecolor=(.5, .5, .5),
                        markeredgecolor=(.5, .5, .5),
                        marker='o', markersize=10, alpha=1)
                # get prob and angle - prob will be shade and angle color
                prob = np.real(np.dot(state[i], state[i].conj()))
                colorstate = phase_to_color_wheel(state[i])
                a = Arrow3D([0, xvalue], [0, yvalue], [0, zvalue],
                            mutation_scale=20, alpha=prob, arrowstyle="-",
                            color=colorstate, lw=10)
                ax.add_artist(a)
            # add weight lines
            for weight in range(d + 1):
                theta = np.linspace(-2 * np.pi, 2 * np.pi, 100)
                z = -2 * weight / d + 1
                r = np.sqrt(1 - z**2)
                x = r * np.cos(theta)
                y = r * np.sin(theta)
                ax.plot(x, y, z, color=(.5, .5, .5))
            # add center point
            ax.plot([0], [0], [0], markerfacecolor=(.5, .5, .5),
                    markeredgecolor=(.5, .5, .5), marker='o', markersize=10,
                    alpha=1)
            we[prob_location] = 0
        else:
            break
    plt.tight_layout()
    plt.close(fig)
    return fig

def plot_state(quantum_state, method='city', figsize=None):
    """Plot the quantum state.

    Args:
        quantum_state (ndarray): statevector or density matrix
                                 representation of a quantum state.
        method (str): Plotting method to use.
        figsize (tuple): Figure size in inches,

    Returns:
         matplotlib.Figure: The matplotlib.Figure of the visualization
    Raises:
        ImportError: Requires matplotlib.
        VisualizationError: if the input is not a statevector or density
        matrix, or if the state is not an multi-qubit quantum state.
    """
    if not HAS_MATPLOTLIB:
        raise ImportError('Must have Matplotlib installed.')
    warnings.warn("plot_state is deprecated, and will be removed in \
                  the 0.9 release. Use the plot_state_ * functions \
                  instead.",
                  DeprecationWarning)
    # Check if input is a statevector, and convert to density matrix
    rho = _validate_input_state(quantum_state)
    fig = None
    if method == 'city':
        fig = plot_state_city(rho, figsize=figsize)
    elif method == "paulivec":
        fig = plot_state_paulivec(rho, figsize=figsize)
    elif method == "qsphere":
        fig = plot_state_qsphere(rho, figsize=figsize)
    elif method == "bloch":
        plot_bloch_multivector(rho, figsize=figsize)
    elif method == "hinton":
        fig = plot_state_hinton(rho, figsize=figsize)
    return fig

def generate_facecolors(x, y, z, dx, dy, dz, color):
    """Generates shaded facecolors for shaded bars.
    This is here to work around a Matplotlib bug
    where alpha does not work in Bar3D.
    Args:
        x (array_like): The x- coordinates of the anchor point of the bars.
        y (array_like): The y- coordinates of the anchor point of the bars.
        z (array_like): The z- coordinates of the anchor point of the bars.
        dx (array_like): Width of bars.
        dy (array_like): Depth of bars.
        dz (array_like): Height of bars.
        color (array_like): sequence of valid color specifications, optional
    Returns:
        list: Shaded colors for bars.
    """
    cuboid = np.array([
        # -z
        (
            (0, 0, 0),
            (0, 1, 0),
            (1, 1, 0),
            (1, 0, 0),
        ),
        # +z
        (
            (0, 0, 1),
            (1, 0, 1),
            (1, 1, 1),
            (0, 1, 1),
        ),
        # -y
        (
            (0, 0, 0),
            (1, 0, 0),
            (1, 0, 1),
            (0, 0, 1),
        ),
        # +y
        (
            (0, 1, 0),
            (0, 1, 1),
            (1, 1, 1),
            (1, 1, 0),
        ),
        # -x
        (
            (0, 0, 0),
            (0, 0, 1),
            (0, 1, 1),
            (0, 1, 0),
        ),
        # +x
        (
            (1, 0, 0),
            (1, 1, 0),
            (1, 1, 1),
            (1, 0, 1),
        ),
    ])

    # indexed by [bar, face, vertex, coord]
    polys = np.empty(x.shape + cuboid.shape)
    # handle each coordinate separately
    for i, p, dp in [(0, x, dx), (1, y, dy), (2, z, dz)]:
        p = p[..., np.newaxis, np.newaxis]
        dp = dp[..., np.newaxis, np.newaxis]
        polys[..., i] = p + dp * cuboid[..., i]

    # collapse the first two axes
    polys = polys.reshape((-1,) + polys.shape[2:])

    facecolors = []
    if len(color) == len(x):
        # bar colors specified, need to expand to number of faces
        for c in color:
            facecolors.extend([c] * 6)
    else:
        # a single color specified, or face colors specified explicitly
        facecolors = list(mcolors.to_rgba_array(color))
        if len(facecolors) < len(x):
            facecolors *= (6 * len(x))

    normals = _generate_normals(polys)
    return _shade_colors(facecolors, normals)

def _generate_normals(polygons):
    """
    Takes a list of polygons and return an array of their normals.
    Normals point towards the viewer for a face with its vertices in
    counterclockwise order, following the right hand rule.
    Uses three points equally spaced around the polygon.
    This normal of course might not make sense for polygons with more than
    three points not lying in a plane, but it's a plausible and fast
    approximation.
    Args:
        polygons (list): list of (M_i, 3) array_like, or (..., M, 3) array_like
            A sequence of polygons to compute normals for, which can have
            varying numbers of vertices. If the polygons all have the same
            number of vertices and array is passed, then the operation will
            be vectorized.
    Returns:
        normals: (..., 3) array_like
            A normal vector estimated for the polygon.
    """
    if isinstance(polygons, np.ndarray):
        # optimization: polygons all have the same number of points, so can
        # vectorize
        n = polygons.shape[-2]
        i1, i2, i3 = 0, n//3, 2*n//3
        v1 = polygons[..., i1, :] - polygons[..., i2, :]
        v2 = polygons[..., i2, :] - polygons[..., i3, :]
    else:
        # The subtraction doesn't vectorize because polygons is jagged.
        v1 = np.empty((len(polygons), 3))
        v2 = np.empty((len(polygons), 3))
        for poly_i, ps in enumerate(polygons):
            n = len(ps)
            i1, i2, i3 = 0, n//3, 2*n//3
            v1[poly_i, :] = ps[i1, :] - ps[i2, :]
            v2[poly_i, :] = ps[i2, :] - ps[i3, :]

    return np.cross(v1, v2)

def _shade_colors(color, normals, lightsource=None):
    """
    Shade *color* using normal vectors given by *normals*.
    *color* can also be an array of the same length as *normals*.
    """
    if lightsource is None:
        # chosen for backwards-compatibility
        lightsource = LightSource(azdeg=225, altdeg=19.4712)

    shade = np.array([np.dot(n / proj3d.mod(n), lightsource.direction)
                      if proj3d.mod(n) else np.nan
                      for n in normals])
    mask = ~np.isnan(shade)

    if mask.any():
        norm = Normalize(min(shade[mask]), max(shade[mask]))
        shade[~mask] = min(shade[mask])
        color = mcolors.to_rgba_array(color)
        # shape of color should be (M, 4) (where M is number of faces)
        # shape of shade should be (M,)
        # colors should have final shape of (M, 4)
        alpha = color[:, 3]
        colors = (0.5 + norm(shade)[:, np.newaxis] * 0.5) * color
        colors[:, 3] = alpha
    else:
        colors = np.asanyarray(color).copy()

    return colors


# -*- coding: utf-8 -*-

# Copyright 2018, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

""" A module for viewing the details of all available devices.
"""

import math
from qiskit.exceptions import QiskitError

try:
    # pylint: disable=import-error,no-name-in-module
    from qiskit.providers.ibmq import IBMQ, IBMQBackend
except ImportError:
    pass

def get_unique_backends():
    """Gets the unique backends that are available.

    Returns:
        list: Unique available backends.

    Raises:
        QiskitError: No backends available.
    """
    backends = IBMQ.backends()
    unique_hardware_backends = []
    unique_names = []
    for back in backends:
        if back.name() not in unique_names and not back.configuration().simulator:
            unique_hardware_backends.append(back)
            unique_names.append(back.name())
    if not unique_hardware_backends:
        raise QiskitError('No backends available.')
    return unique_hardware_backends

def backend_monitor(backend):
    """Monitor a single IBMQ backend.

    Args:
        backend (IBMQBackend): Backend to monitor.
    Raises:
        QiskitError: Input is not a IBMQ backend.
    """
    if not isinstance(backend, IBMQBackend):
        raise QiskitError('Input variable is not of type IBMQBackend.')
    config = backend.configuration().to_dict()
    status = backend.status().to_dict()
    config_dict = {**status, **config}
    if not config['simulator']:
        props = backend.properties().to_dict()

    print(backend.name())
    print('='*len(backend.name()))
    print('Configuration')
    print('-'*13)
    offset = '    '

    upper_list = ['n_qubits', 'operational',
                  'status_msg', 'pending_jobs',
                  'basis_gates', 'local', 'simulator']

    lower_list = list(set(config_dict.keys()).difference(upper_list))
    # Remove gates because they are in a different tab
    lower_list.remove('gates')
    for item in upper_list+lower_list:
        print(offset+item+':', config_dict[item])

    # Stop here if simulator
    if config['simulator']:
        return

    print()
    qubit_header = 'Qubits [Name / Freq / T1 / T2 / U1 err / U2 err / U3 err / Readout err]'
    print(qubit_header)
    print('-'*len(qubit_header))

    sep = ' / '
    for qub in range(len(props['qubits'])):
        name = 'Q%s' % qub
        qubit_data = props['qubits'][qub]
        gate_data = props['gates'][3*qub:3*qub+3]
        t1_info = qubit_data[0]
        t2_info = qubit_data[1]
        freq_info = qubit_data[2]
        readout_info = qubit_data[3]

        freq = str(round(freq_info['value'], 5))+' '+freq_info['unit']
        T1 = str(round(t1_info['value'],  # pylint: disable=invalid-name
                       5))+' ' + t1_info['unit']
        T2 = str(round(t2_info['value'],  # pylint: disable=invalid-name
                       5))+' ' + t2_info['unit']
        # pylint: disable=invalid-name
        U1 = str(round(gate_data[0]['parameters'][0]['value'], 5))
        # pylint: disable=invalid-name
        U2 = str(round(gate_data[1]['parameters'][0]['value'], 5))
        # pylint: disable=invalid-name
        U3 = str(round(gate_data[2]['parameters'][0]['value'], 5))

        readout_error = str(round(readout_info['value'], 5))

        qstr = sep.join([name, freq, T1, T2, U1, U2, U3, readout_error])
        print(offset+qstr)

    print()
    multi_qubit_gates = props['gates'][3*config['n_qubits']:]
    multi_header = 'Multi-Qubit Gates [Name / Type / Gate Error]'
    print(multi_header)
    print('-'*len(multi_header))

    for gate in multi_qubit_gates:
        name = gate['name']
        ttype = gate['gate']
        error = str(round(gate['parameters'][0]['value'], 5))
        mstr = sep.join([name, ttype, error])
        print(offset+mstr)

def backend_overview():
    """Gives overview information on all the IBMQ
    backends that are available.
    """
    unique_hardware_backends = get_unique_backends()
    _backends = []
    # Sort backends by operational or not
    for idx, back in enumerate(unique_hardware_backends):
        if back.status().operational:
            _backends = [back] + _backends
        else:
            _backends = _backends + [back]

    stati = [back.status() for back in _backends]
    idx = list(range(len(_backends)))
    pending = [s.pending_jobs for s in stati]
    _, least_idx = zip(*sorted(zip(pending, idx)))

    # Make sure least pending is operational
    for ind in least_idx:
        if stati[ind].operational:
            least_pending_idx = ind
            break

    num_rows = math.ceil(len(_backends)/3)

    count = 0
    num_backends = len(_backends)
    for _ in range(num_rows):
        max_len = 0
        str_list = ['']*8
        for idx in range(3):
            offset = ' ' * 10 if idx else ''
            config = _backends[count].configuration().to_dict()
            props = _backends[count].properties().to_dict()
            n_qubits = config['n_qubits']
            str_list[0] += (' '*(max_len-len(str_list[0]))+offset)
            str_list[0] += _backends[count].name()

            str_list[1] += (' '*(max_len-len(str_list[1]))+offset)
            str_list[1] += '-'*len(_backends[count].name())

            str_list[2] += (' '*(max_len-len(str_list[2]))+offset)
            str_list[2] += 'Num. Qubits:  %s' % config['n_qubits']

            str_list[3] += (' '*(max_len-len(str_list[3]))+offset)
            str_list[3] += 'Pending Jobs: %s' % stati[count].pending_jobs

            str_list[4] += (' '*(max_len-len(str_list[4]))+offset)
            str_list[4] += 'Least busy:   %s' % (count == least_pending_idx)

            str_list[5] += (' '*(max_len-len(str_list[5]))+offset)
            str_list[5] += 'Operational:  %s' % stati[count].operational

            str_list[6] += (' '*(max_len-len(str_list[6]))+offset)
            str_list[6] += 'Avg. T1:      %s' % round(sum([q[0]['value']
                                                           for q in props['qubits']])/n_qubits, 1)
            str_list[7] += (' '*(max_len-len(str_list[7]))+offset)
            str_list[7] += 'Avg. T2:      %s' % round(sum([q[1]['value']
                                                           for q in props['qubits']])/n_qubits, 1)
            count += 1
            if count == num_backends:
                break
            max_len = max([len(s) for s in str_list])

        print("\n".join(str_list))
        print('\n'*2)


# -*- coding: utf-8 -*-

# Copyright 2019, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

"""Object to represent the information at a node in the DAGCircuit
"""

from qiskit.exceptions import QiskitError

class DAGNode:
    """
    Object to represent the information at a node in the DAGCircuit

    It is used as the return value from *_nodes() functions and can
    be supplied to functions that take a node.
    """

    def __init__(self, data_dict, nid=-1):
        """Create a node """
        self._node_id = nid
        self.data_dict = data_dict

    @property
    def type(self):
        """Returns a str which is the type of the node else None"""
        return self.data_dict.get('type')

    @property
    def op(self):
        """Returns the Instruction object corresponding to the op for the node else None"""
        if 'type' not in self.data_dict or self.data_dict['type'] != 'op':
            raise QiskitError("The node %s is not an op node" % (str(self)))
        return self.data_dict.get('op')

    @property
    def name(self):
        """Returns a str which is the name of the node else None"""
        return self.data_dict.get('name')

    @name.setter
    def name(self, new_name):
        """Sets the name of the node to be the given value"""
        self.data_dict['name'] = new_name

    @property
    def qargs(self):
        """
        Returns list of (QuantumRegister, int) tuples where the int is the index
        of the qubit else an empty list
        """
        return self.data_dict.get('qargs', [])

    @qargs.setter
    def qargs(self, new_qargs):
        """Sets the qargs to be the given list of qargs"""
        self.data_dict['qargs'] = new_qargs

    @property
    def cargs(self):
        """
        Returns list of (ClassicalRegister, int) tuples where the int is the index
        of the cbit else an empty list
        """
        return self.data_dict.get('cargs', [])

    @property
    def condition(self):
        """
        Returns a tuple (ClassicalRegister, int) where the int is the
        value of the condition else None
        """
        return self.data_dict.get('condition')

    @property
    def wire(self):
        """
        Returns (Register, int) tuple where the int is the index of
        the wire else None
        """
        if self.data_dict['type'] not in ['in', 'out']:
            raise QiskitError('The node %s is not an input/output node' % str(self))
        return self.data_dict.get('wire')

    def __lt__(self, other):
        return self._node_id < other._node_id

    def __gt__(self, other):
        return self._node_id > other._node_id

    def __hash__(self):
        """Needed for ancestors function, which returns a set
        to be in a set requires the object to be hashable
        """
        return hash(id(self))

    def __str__(self):
        # TODO is this used anywhere other than in DAG drawing?
        # needs to be unique as it is what pydot uses to distinguish nodes
        return str(id(self))

    def pop(self, val):
        """Remove the provided value from the dictionary"""
        del self.data_dict[val]

    @staticmethod
    def semantic_eq(node1, node2):
        """
        Check if DAG nodes are considered equivalent, e.g. as a node_match for nx.is_isomorphic.

        Args:
            node1 (DAGNode): A node to compare.
            node2 (DAGNode): The other node to compare.

        Return:
            Bool: If node1 == node2
        """
        # For barriers, qarg order is not significant so compare as sets
        if 'barrier' == node1.name == node2.name:
            return set(node1.qargs) == set(node2.qargs)
        return node1.data_dict == node2.data_dict


# -*- coding: utf-8 -*-

# Copyright 2019, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

# pylint: disable=missing-return-doc, invalid-name

"""Module for builtin discrete pulses.

Note the sampling strategy use for all discrete pulses is `left`.
"""

from qiskit.pulse.commands import SamplePulse
from qiskit.pulse.pulse_lib import continuous
from qiskit.pulse import samplers

_sampled_constant_pulse = samplers.left(continuous.constant)

def constant(duration: int, amp: complex, name: str = None) -> SamplePulse:
    """Generates constant-sampled `SamplePulse`.

    Applies `left` sampling strategy to generate discrete pulse from continuous function.

    Args:
        duration: Duration of pulse. Must be greater than zero.
        amp: Complex pulse amplitude.
        name: Name of pulse.
    """
    return _sampled_constant_pulse(duration, amp, name=name)

_sampled_zero_pulse = samplers.left(continuous.zero)

def zero(duration: int, name: str = None) -> SamplePulse:
    """Generates zero-sampled `SamplePulse`.

    Args:
        duration: Duration of pulse. Must be greater than zero.
        name: Name of pulse.
    """
    return _sampled_zero_pulse(duration, name=name)

_sampled_square_pulse = samplers.left(continuous.square)

def square(duration: int, amp: complex, period: float = None,
           phase: float = 0, name: str = None) -> SamplePulse:
    """Generates square wave `SamplePulse`.

    Applies `left` sampling strategy to generate discrete pulse from continuous function.

    Args:
        duration: Duration of pulse. Must be greater than zero.
        amp: Pulse amplitude. Wave range is [-amp, amp].
        period: Pulse period, units of dt. If `None` defaults to single cycle.
        phase: Pulse phase.
        name: Name of pulse.
    """
    if period is None:
        period = duration

    return _sampled_square_pulse(duration, amp, period, phase=phase, name=name)

_sampled_sawtooth_pulse = samplers.left(continuous.sawtooth)

def sawtooth(duration: int, amp: complex, period: float = None,
             phase: float = 0, name: str = None) -> SamplePulse:
    """Generates sawtooth wave `SamplePulse`.

    Args:
        duration: Duration of pulse. Must be greater than zero.
        amp: Pulse amplitude. Wave range is [-amp, amp].
        period: Pulse period, units of dt. If `None` defaults to single cycle.
        phase: Pulse phase.
        name: Name of pulse.
    """
    if period is None:
        period = duration

    return _sampled_sawtooth_pulse(duration, amp, period, phase=phase, name=name)

_sampled_triangle_pulse = samplers.left(continuous.triangle)

def triangle(duration: int, amp: complex, period: float = None,
             phase: float = 0, name: str = None) -> SamplePulse:
    """Generates triangle wave `SamplePulse`.

    Applies `left` sampling strategy to generate discrete pulse from continuous function.

    Args:
        duration: Duration of pulse. Must be greater than zero.
        amp: Pulse amplitude. Wave range is [-amp, amp].
        period: Pulse period, units of dt. If `None` defaults to single cycle.
        phase: Pulse phase.
        name: Name of pulse.
    """
    if period is None:
        period = duration

    return _sampled_triangle_pulse(duration, amp, period, phase=phase, name=name)

_sampled_cos_pulse = samplers.left(continuous.cos)

def cos(duration: int, amp: complex, freq: float = None,
        phase: float = 0, name: str = None) -> SamplePulse:
    """Generates cosine wave `SamplePulse`.

    Applies `left` sampling strategy to generate discrete pulse from continuous function.

    Args:
        duration: Duration of pulse. Must be greater than zero.
        amp: Pulse amplitude.
        freq: Pulse frequency, units of 1/dt. If `None` defaults to single cycle.
        phase: Pulse phase.
        name: Name of pulse.
    """
    if freq is None:
        freq = 1/duration

    return _sampled_cos_pulse(duration, amp, freq, phase=phase, name=name)

_sampled_sin_pulse = samplers.left(continuous.sin)

def sin(duration: int, amp: complex, freq: float = None,
        phase: float = 0, name: str = None) -> SamplePulse:
    """Generates sine wave `SamplePulse`.

    Args:
        duration: Duration of pulse. Must be greater than zero.
        amp: Pulse amplitude.
        freq: Pulse frequency, units of 1/dt. If `None` defaults to single cycle.
        phase: Pulse phase.
        name: Name of pulse.
    """
    if freq is None:
        freq = 1/duration

    return _sampled_sin_pulse(duration, amp, freq, phase=phase, name=name)

_sampled_gaussian_pulse = samplers.left(continuous.gaussian)

def gaussian(duration: int, amp: complex, sigma: float, name: str = None) -> SamplePulse:
    r"""Generates unnormalized gaussian `SamplePulse`.

    Centered at `duration/2` and zeroed at `t=-1` to prevent large initial discontinuity.

    Applies `left` sampling strategy to generate discrete pulse from continuous function.

    Integrated area under curve is $\Omega_g(amp, sigma) = amp \times np.sqrt(2\pi \sigma^2)$

    Args:
        duration: Duration of pulse. Must be greater than zero.
        amp: Pulse amplitude at `duration/2`.
        sigma: Width (standard deviation) of pulse.
        name: Name of pulse.
    """
    center = duration/2
    zeroed_width = duration + 2
    return _sampled_gaussian_pulse(duration, amp, center, sigma,
                                   zeroed_width=zeroed_width, rescale_amp=True, name=name)

_sampled_gaussian_deriv_pulse = samplers.left(continuous.gaussian_deriv)

def gaussian_deriv(duration: int, amp: complex, sigma: float, name: str = None) -> SamplePulse:
    r"""Generates unnormalized gaussian derivative `SamplePulse`.

    Applies `left` sampling strategy to generate discrete pulse from continuous function.

    Args:
        duration: Duration of pulse. Must be greater than zero.
        amp: Pulse amplitude at `center`.
        sigma: Width (standard deviation) of pulse.
        name: Name of pulse.
    """
    center = duration/2
    return _sampled_gaussian_deriv_pulse(duration, amp, center, sigma, name=name)

_sampled_gaussian_square_pulse = samplers.left(continuous.gaussian_square)

def gaussian_square(duration: int, amp: complex, sigma: float,
                    risefall: int, name: str = None) -> SamplePulse:
    """Generates gaussian square `SamplePulse`.

    Centered at `duration/2` and zeroed at `t=-1` and `t=duration+1` to prevent
    large initial/final discontinuities.

    Applies `left` sampling strategy to generate discrete pulse from continuous function.

    Args:
        duration: Duration of pulse. Must be greater than zero.
        amp: Pulse amplitude.
        sigma: Width (standard deviation) of gaussian rise/fall portion of the pulse.
        risefall: Number of samples over which pulse rise and fall happen. Width of
            square portion of pulse will be `duration-2*risefall`.
        name: Name of pulse.
    """
    center = duration/2
    width = duration-2*risefall
    zeroed_width = duration + 2
    return _sampled_gaussian_square_pulse(duration, amp, center, width, sigma,
                                          zeroed_width=zeroed_width, name=name)

_sampled_drag_pulse = samplers.left(continuous.drag)

def drag(duration: int, amp: complex, sigma: float, beta: float, name: str = None) -> SamplePulse:
    r"""Generates Y-only correction DRAG `SamplePulse` for standard nonlinear oscillator (SNO) [1].

    Centered at `duration/2` and zeroed at `t=-1` to prevent large initial discontinuity.

    Applies `left` sampling strategy to generate discrete pulse from continuous function.

    [1] Gambetta, J. M., Motzoi, F., Merkel, S. T. & Wilhelm, F. K.
        Analytic control methods for high-fidelity unitary operations
        in a weakly nonlinear oscillator. Phys. Rev. A 83, 012308 (2011).

    Args:
        duration: Duration of pulse. Must be greater than zero.
        amp: Pulse amplitude at `center`.
        sigma: Width (standard deviation) of pulse.
        beta: Y correction amplitude. For the SNO this is $\beta=-\frac{\lambda_1^2}{4\Delta_2}$.
            Where $\lambds_1$ is the relative coupling strength between the first excited and second
            excited states and $\Delta_2$ is the detuning between the resepective excited states.
        name: Name of pulse.
    """
    center = duration/2
    zeroed_width = duration + 2
    return _sampled_drag_pulse(duration, amp, center, sigma, beta,
                               zeroed_width=zeroed_width, rescale_amp=True, name=name)


# -*- coding: utf-8 -*-

# Copyright 2019, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

"""
Transpiler pass to remove diagonal gates (like RZ, T, Z, etc) before
a measurement. Including diagonal 2Q gates.
"""

from qiskit.circuit import Measure
from qiskit.extensions.standard import RZGate, ZGate, TGate, SGate, TdgGate, SdgGate, U1Gate,\
    CzGate, CrzGate, Cu1Gate, RZZGate
from qiskit.transpiler.basepasses import TransformationPass

class RemoveDiagonalGatesBeforeMeasure(TransformationPass):
    """Remove diagonal gates (like RZ, T, Z, etc) before a measurement.
    Including diagonal 2Q gates."""

    def run(self, dag):
        """Return a new circuit that has been optimized."""
        diagonal_1q_gates = (RZGate, ZGate, TGate, SGate, TdgGate, SdgGate, U1Gate)
        diagonal_2q_gates = (CzGate, CrzGate, Cu1Gate, RZZGate)

        nodes_to_remove = set()
        for measure in dag.op_nodes(Measure):
            predecessor = dag.quantum_predecessors(measure)[0]

            if predecessor.type == 'op' and isinstance(predecessor.op, diagonal_1q_gates):
                nodes_to_remove.add(predecessor)

            if predecessor.type == 'op' and isinstance(predecessor.op, diagonal_2q_gates):
                successors = dag.quantum_successors(predecessor)
                if all([s.type == 'op' and isinstance(s.op, Measure) for s in successors]):
                    nodes_to_remove.add(predecessor)

        for node_to_remove in nodes_to_remove:
            dag.remove_op_node(node_to_remove)

        return dag


# -*- coding: utf-8 -*-

# Copyright 2018, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

"""A module for visualizing device coupling maps"""

from qiskit.exceptions import QiskitError
from .matplotlib import HAS_MATPLOTLIB
if HAS_MATPLOTLIB:
    import matplotlib.pyplot as plt  # pylint: disable=import-error
    import matplotlib.patches as mpatches

class _GraphDist():
    """Transform the circles properly for non-square axes.
    """
    def __init__(self, size, ax, x=True):
        self.size = size
        self.ax = ax  # pylint: disable=invalid-name
        self.x = x

    @property
    def dist_real(self):
        """Compute distance.
        """
        x0, y0 = self.ax.transAxes.transform(  # pylint: disable=invalid-name
            (0, 0))
        x1, y1 = self.ax.transAxes.transform(  # pylint: disable=invalid-name
            (1, 1))
        value = x1 - x0 if self.x else y1 - y0
        return value

    @property
    def dist_abs(self):
        """Distance abs
        """
        bounds = self.ax.get_xlim() if self.x else self.ax.get_ylim()
        return bounds[0] - bounds[1]

    @property
    def value(self):
        """Return value.
        """
        return (self.size / self.dist_real) * self.dist_abs

    def __mul__(self, obj):
        return self.value * obj

def plot_gate_map(backend, figsize=None,
                  plot_directed=False,
                  label_qubits=True,
                  qubit_size=24,
                  line_width=4,
                  font_size=12,
                  qubit_color=None,
                  line_color=None,
                  font_color='w'):
    """Plots the gate map of a device.

    Args:
        backend (BaseBackend): A backend instance,
        figsize (tuple): Output figure size (wxh) in inches.
        plot_directed (bool): Plot directed coupling map.
        label_qubits (bool): Label the qubits.
        qubit_size (float): Size of qubit marker.
        line_width (float): Width of lines.
        font_size (int): Font size of qubit labels.
        qubit_color (list): A list of colors for the qubits
        line_color (list): A list of colors for each line from coupling_map.
        font_color (str): The font color for the qubit labels.

    Returns:
        Figure: A Matplotlib figure instance.

    Raises:
        QiskitError: if tried to pass a simulator.
        ImportError: if matplotlib not installed.
    """
    if not HAS_MATPLOTLIB:
        raise ImportError('Must have Matplotlib installed.')

    if backend.configuration().simulator:
        raise QiskitError('Requires a device backend, not simulator.')

    mpl_data = {}

    mpl_data['ibmq_20_tokyo'] = [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4],
                                 [1, 0], [1, 1], [1, 2], [1, 3], [1, 4],
                                 [2, 0], [2, 1], [2, 2], [2, 3], [2, 4],
                                 [3, 0], [3, 1], [3, 2], [3, 3], [3, 4]]

    mpl_data['ibmq_poughkeepsie'] = mpl_data['ibmq_20_tokyo']

    mpl_data['ibmq_16_melbourne'] = [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4],
                                     [0, 5], [0, 6], [1, 7], [1, 6], [1, 5],
                                     [1, 4], [1, 3], [1, 2], [1, 1]]

    mpl_data['ibmq_16_rueschlikon'] = [[1, 0], [0, 0], [0, 1], [0, 2], [0, 3],
                                       [0, 4], [0, 5], [0, 6], [0, 7], [1, 7],
                                       [1, 6], [1, 5], [1, 4], [1, 3], [1, 2], [1, 1]]

    mpl_data['ibmq_5_tenerife'] = [[1, 0], [0, 1], [1, 1], [1, 2], [2, 1]]

    mpl_data['ibmq_5_yorktown'] = mpl_data['ibmq_5_tenerife']

    config = backend.configuration()
    name = config.backend_name
    cmap = config.coupling_map

    dep_names = {'ibmqx5': 'ibmq_16_rueschlikon',
                 'ibmqx4': 'ibmq_5_tenerife',
                 'ibmqx2': 'ibmq_5_yorktown'}

    if name in dep_names.keys():
        name = dep_names[name]

    if name in mpl_data.keys():
        grid_data = mpl_data[name]
    else:
        fig, ax = plt.subplots(figsize=(5, 5))  # pylint: disable=invalid-name
        ax.axis('off')
        return fig

    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)

    if figsize is None:
        if x_max/max_dim > 0.33 and y_max/max_dim > 0.33:
            figsize = (5, 5)
        else:
            figsize = (9, 3)

    fig, ax = plt.subplots(figsize=figsize)  # pylint: disable=invalid-name
    ax.axis('off')
    fig.tight_layout()

    # set coloring
    if qubit_color is None:
        qubit_color = ['#648fff']*config.n_qubits
    if line_color is None:
        line_color = ['#648fff']*len(cmap)

    # Add lines for couplings
    for ind, edge in enumerate(cmap):
        is_symmetric = False
        if edge[::-1] in cmap:
            is_symmetric = True
        y_start = grid_data[edge[0]][0]
        x_start = grid_data[edge[0]][1]
        y_end = grid_data[edge[1]][0]
        x_end = grid_data[edge[1]][1]

        if is_symmetric:
            if y_start == y_end:
                x_end = (x_end - x_start)/2+x_start

            elif x_start == x_end:
                y_end = (y_end - y_start)/2+y_start

            else:
                x_end = (x_end - x_start)/2+x_start
                y_end = (y_end - y_start)/2+y_start
        ax.add_artist(plt.Line2D([x_start, x_end], [-y_start, -y_end],
                                 color=line_color[ind], linewidth=line_width,
                                 zorder=0))
        if plot_directed:
            dx = x_end-x_start  # pylint: disable=invalid-name
            dy = y_end-y_start  # pylint: disable=invalid-name
            if is_symmetric:
                x_arrow = x_start+dx*0.95
                y_arrow = -y_start-dy*0.95
                dx_arrow = dx*0.01
                dy_arrow = -dy*0.01
                head_width = 0.15
            else:
                x_arrow = x_start+dx*0.5
                y_arrow = -y_start-dy*0.5
                dx_arrow = dx*0.2
                dy_arrow = -dy*0.2
                head_width = 0.2
            ax.add_patch(mpatches.FancyArrow(x_arrow,
                                             y_arrow,
                                             dx_arrow,
                                             dy_arrow,
                                             head_width=head_width,
                                             length_includes_head=True,
                                             edgecolor=None,
                                             linewidth=0,
                                             facecolor=line_color[ind],
                                             zorder=1))

    # Add circles for qubits
    for var, idx in enumerate(grid_data):
        _idx = [idx[1], -idx[0]]
        width = _GraphDist(qubit_size, ax, True)
        height = _GraphDist(qubit_size, ax, False)
        ax.add_artist(mpatches.Ellipse(
            _idx, width, height, color=qubit_color[var], zorder=1))
        if label_qubits:
            ax.text(*_idx, s=str(var),
                    horizontalalignment='center',
                    verticalalignment='center',
                    color=font_color, size=font_size, weight='bold')
    ax.set_xlim([-1, x_max+1])
    ax.set_ylim([-(y_max+1), 1])
    plt.close(fig)
    return fig


# -*- coding: utf-8 -*-

# Copyright 2017, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

# pylint: disable=invalid-name

"""Node for an OPENQASM qreg statement."""

from .node import Node

class Qreg(Node):
    """Node for an OPENQASM qreg statement.

    children[0] is an indexedid node.
    """

    def __init__(self, children):
        """Create the qreg node."""
        super().__init__('qreg', children, None)
        # This is the indexed id, the full "id[n]" object
        self.id = children[0]
        # Name of the qreg
        self.name = self.id.name
        # Source line number
        self.line = self.id.line
        # Source file name
        self.file = self.id.file
        # Size of the register
        self.index = self.id.index

    def to_string(self, indent):
        """Print the node data, with indent."""
        ind = indent * ' '
        print(ind, 'qreg')
        self.children[0].to_string(indent + 3)

    def qasm(self, prec=15):
        """Return the corresponding OPENQASM string."""
        return "qreg " + self.id.qasm(prec) + ";"


# -*- coding: utf-8 -*-

# Copyright 2017, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

"""Provider for Basic Aer simulator backends."""

from collections import OrderedDict
import logging

from qiskit.exceptions import QiskitError
from qiskit.providers import BaseProvider
from qiskit.providers.exceptions import QiskitBackendNotFoundError
from qiskit.providers.providerutils import resolve_backend_name, filter_backends

from .qasm_simulator import QasmSimulatorPy
from .statevector_simulator import StatevectorSimulatorPy
from .unitary_simulator import UnitarySimulatorPy

logger = logging.getLogger(__name__)

SIMULATORS = [
    QasmSimulatorPy,
    StatevectorSimulatorPy,
    UnitarySimulatorPy
]

class BasicAerProvider(BaseProvider):
    """Provider for Basic Aer backends."""

    def __init__(self, *args, **kwargs):
        super().__init__(args, kwargs)

        # Populate the list of Basic Aer backends.
        self._backends = self._verify_backends()

    def get_backend(self, name=None, **kwargs):
        backends = self._backends.values()

        # Special handling of the `name` parameter, to support alias resolution
        # and deprecated names.
        if name:
            try:
                resolved_name = resolve_backend_name(
                    name, backends,
                    self._deprecated_backend_names(),
                    {}
                )
                name = resolved_name
            except LookupError:
                raise QiskitBackendNotFoundError(
                    "The '{}' backend is not installed in your system.".format(name))

        return super().get_backend(name=name, **kwargs)

    def backends(self, name=None, filters=None, **kwargs):
        # pylint: disable=arguments-differ
        backends = self._backends.values()

        # Special handling of the `name` parameter, to support alias resolution
        # and deprecated names.
        if name:
            try:
                resolved_name = resolve_backend_name(
                    name, backends,
                    self._deprecated_backend_names(),
                    {}
                )
                backends = [backend for backend in backends if
                            backend.name() == resolved_name]
            except LookupError:
                return []

        return filter_backends(backends, filters=filters, **kwargs)

    @staticmethod
    def _deprecated_backend_names():
        """Returns deprecated backend names."""
        return {
            'qasm_simulator_py': 'qasm_simulator',
            'statevector_simulator_py': 'statevector_simulator',
            'unitary_simulator_py': 'unitary_simulator',
            'local_qasm_simulator_py': 'qasm_simulator',
            'local_statevector_simulator_py': 'statevector_simulator',
            'local_unitary_simulator_py': 'unitary_simulator',
            'local_unitary_simulator': 'unitary_simulator',
            }

    def _verify_backends(self):
        """
        Return the Basic Aer backends in `BACKENDS` that are
        effectively available (as some of them might depend on the presence
        of an optional dependency or on the existence of a binary).

        Returns:
            dict[str:BaseBackend]: a dict of Basic Aer backend instances for
                the backends that could be instantiated, keyed by backend name.
        """
        ret = OrderedDict()
        for backend_cls in SIMULATORS:
            try:
                backend_instance = self._get_backend_instance(backend_cls)
                backend_name = backend_instance.name()
                ret[backend_name] = backend_instance
            except QiskitError as err:
                # Ignore backends that could not be initialized.
                logger.info('Basic Aer backend %s is not available: %s',
                            backend_cls, str(err))
        return ret

    def _get_backend_instance(self, backend_cls):
        """
        Return an instance of a backend from its class.

        Args:
            backend_cls (class): Backend class.
        Returns:
            BaseBackend: a backend instance.
        Raises:
            QiskitError: if the backend could not be instantiated.
        """
        # Verify that the backend can be instantiated.
        try:
            backend_instance = backend_cls(provider=self)
        except Exception as err:
            raise QiskitError('Backend %s could not be instantiated: %s' %
                              (backend_cls, err))

        return backend_instance

    def __str__(self):
        return 'BasicAer'