# -*- 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.

"""
Base register reference object.
"""
import re
import logging
import itertools

from qiskit.exceptions import QiskitError, QiskitIndexError

logger = logging.getLogger(__name__)

class Register:
    """Implement a generic register."""

    # Counter for the number of instances in this class.
    instances_counter = itertools.count()
    # Prefix to use for auto naming.
    prefix = 'reg'

    def __init__(self, size, name=None):
        """Create a new generic register.
        """

        if name is None:
            name = '%s%i' % (self.prefix, next(self.instances_counter))

        if not isinstance(name, str):
            raise QiskitError("The circuit name should be a string "
                              "(or None for autogenerate a name).")

        test = re.compile('[a-z][a-zA-Z0-9_]*')
        if test.match(name) is None:
            raise QiskitError("%s is an invalid OPENQASM register name." % name)

        self.name = name
        self.size = size
        if size <= 0:
            raise QiskitError("register size must be positive")

    def __repr__(self):
        """Return the official string representing the register."""
        return "%s(%d, '%s')" % (self.__class__.__qualname__,
                                 self.size, self.name)

    def __len__(self):
        """Return register size"""
        return self.size

    def check_range(self, j):
        """Check that j is a valid index into self."""
        if isinstance(j, int):
            if j < 0 or j >= self.size:
                raise QiskitIndexError("register index out of range")
            elif isinstance(j, slice):
                if j.start < 0 or j.stop >= self.size or (j.step is not None and
                                                          j.step <= 0):
                    raise QiskitIndexError("register index slice out of range")

    def __getitem__(self, key):
        """
        Arg:
            key (int|slice|list): index of the bit/qubit to be retrieved.

        Returns:
            tuple[Register, int]: a tuple in the form `(self, key)` if key is int.
                If key is a slice, return a `list((self,key))`.

        Raises:
            QiskitError: if the `key` is not an integer.
            QiskitIndexError: if the `key` is not in the range
                `(0, self.size)`.
        """
        if not isinstance(key, (int, slice, list)):
            raise QiskitError("expected integer or slice index into register")
        if isinstance(key, int) and key < 0:
            key = self.size + key
        self.check_range(key)
        if isinstance(key, slice):
            return [(self, ind) for ind in range(*key.indices(len(self)))]
        elif isinstance(key, list):  # list of qubit indices
            if max(key) < len(self):
                return [(self, ind) for ind in key]
            else:
                raise QiskitError('register index out of range')
        else:
            return self, key

    def __iter__(self):
        """
        Returns:
            iterator: an iterator over the bits/qubits of the register, in the
                form `tuple (Register, int)`.
        """
        return zip([self]*self.size, range(self.size))

    def __eq__(self, other):
        """Two Registers are the same if they are of the same type
        (i.e. quantum/classical), and have the same name and size.

        Args:
            other (Register): other Register

        Returns:
            bool: are self and other equal.
        """
        res = False
        if type(self) is type(other) and \
                self.name == other.name and \
                self.size == other.size:
            res = True
        return res

    def __hash__(self):
        """Make object hashable, based on the name and size to hash."""
        return hash((type(self), self.name, self.size))


# -*- 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.

"""Base node object for the OPENQASM syntax tree."""

class Node:
    """Base node object for the OPENQASM syntax tree."""

    def __init__(self, type, children=None, root=None):
        """Construct a new node object."""
        # pylint: disable=redefined-builtin
        self.type = type
        if children:
            self.children = children
        else:
            self.children = []
        self.root = root
        # True if this node is an expression node, False otherwise
        self.expression = False

    def is_expression(self):
        """Return True if this is an expression node."""
        return self.expression

    def add_child(self, node):
        """Add a child node."""
        self.children.append(node)

    def to_string(self, indent):
        """Print with indent."""
        ind = indent * ' '
        if self.root:
            print(ind, self.type, '---', self.root)
        else:
            print(ind, self.type)
        indent = indent + 3
        ind = indent * ' '
        for children in self.children:
            if children is None:
                print("OOPS! type of parent is", type(self))
                print(self.children)
            if isinstance(children, str):
                print(ind, children)
            elif isinstance(children, int):
                print(ind, str(children))
            elif isinstance(children, float):
                print(ind, str(children))
            else:
                children.to_string(indent)


# -*- 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.

"""
Unitary gate.
"""

from qiskit.exceptions import QiskitError
from .instruction import Instruction

class Gate(Instruction):
    """Unitary gate."""

    def __init__(self, name, num_qubits, params, label=None):
        """Create a new gate.

        Args:
            name (str): the Qobj name of the gate
            num_qubits (int): the number of qubits the gate acts on.
            params (list): a list of parameters.
            label (str or None): An optional label for the gate [Default: None]
        """
        self._label = label
        super().__init__(name, num_qubits, 0, params)

    def to_matrix(self):
        """Return a Numpy.array for the gate unitary matrix.

        Additional Information
        ----------------------
        If a Gate subclass does not implement this method an exception
        will be raised when this base class method is called.
        """
        raise QiskitError("to_matrix not defined for this {}".format(type(self)))

    def assemble(self):
        """Assemble a QasmQobjInstruction"""
        instruction = super().assemble()
        if self.label:
            instruction.label = self.label
        return instruction

    @property
    def label(self):
        """Return gate label"""
        return self._label

    @label.setter
    def label(self, name):
        """Set gate label to name

        Args:
            name (str or None): label to assign unitary

        Raises:
            TypeError: name is not string or None.
        """
        if isinstance(name, (str, type(None))):
            self._label = name
        else:
            raise TypeError('label expects a string or None')


# -*- 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=len-as-condition
"""
Predicates for operators.
"""

import numpy as np

ATOL_DEFAULT = 1e-8
RTOL_DEFAULT = 1e-5

def matrix_equal(mat1,
                 mat2,
                 ignore_phase=False,
                 rtol=RTOL_DEFAULT,
                 atol=ATOL_DEFAULT):
    """Test if two arrays are equal.

    The final comparison is implemented using Numpy.allclose. See its
    documentation for additional information on tolerance parameters.

    If ignore_phase is True both matrices will be multiplied by
    exp(-1j * theta) where `theta` is the first nphase for a
    first non-zero matrix element `|a| * exp(1j * theta)`.

    Args:
        mat1 (matrix_like): a matrix
        mat2 (matrix_like): a matrix
        ignore_phase (bool): ignore complex-phase differences between
            matrices [Default: False]
        rtol (double): the relative tolerance parameter [Default {}].
        atol (double): the absolute tolerance parameter [Default {}].

    Returns:
        bool: True if the matrices are equal or False otherwise.
    """.format(RTOL_DEFAULT, ATOL_DEFAULT)

    if atol is None:
        atol = ATOL_DEFAULT
    if rtol is None:
        rtol = RTOL_DEFAULT
    mat1 = np.array(mat1)
    mat2 = np.array(mat2)
    if mat1.shape != mat2.shape:
        return False
    if ignore_phase:
        # Get phase of first non-zero entry of mat1 and mat2
        # and multiply all entries by the conjugate
        phases1 = np.angle(mat1[mat1 != 0].ravel(order='F'))
        if len(phases1) > 0:
            mat1 = np.exp(-1j * phases1[0]) * mat1
        phases2 = np.angle(mat2[mat2 != 0].ravel(order='F'))
        if len(phases2) > 0:
            mat2 = np.exp(-1j * phases2[0]) * mat2
    return np.allclose(mat1, mat2, rtol=rtol, atol=atol)

def is_square_matrix(mat):
    """Test if an array is a square matrix."""
    mat = np.array(mat)
    if mat.ndim != 2:
        return False
    shape = mat.shape
    return shape[0] == shape[1]

def is_diagonal_matrix(mat, rtol=RTOL_DEFAULT, atol=ATOL_DEFAULT):
    """Test if an array is a diagonal matrix"""
    if atol is None:
        atol = ATOL_DEFAULT
    if rtol is None:
        rtol = RTOL_DEFAULT
    mat = np.array(mat)
    if mat.ndim != 2:
        return False
    return np.allclose(mat, np.diag(np.diagonal(mat)), rtol=rtol, atol=atol)

def is_symmetric_matrix(op, rtol=RTOL_DEFAULT, atol=ATOL_DEFAULT):
    """Test if an array is a symmetrix matrix"""
    if atol is None:
        atol = ATOL_DEFAULT
    if rtol is None:
        rtol = RTOL_DEFAULT
    mat = np.array(op)
    if mat.ndim != 2:
        return False
    return np.allclose(mat, mat.T, rtol=rtol, atol=atol)

def is_hermitian_matrix(mat, rtol=RTOL_DEFAULT, atol=ATOL_DEFAULT):
    """Test if an array is a Hermitian matrix"""
    if atol is None:
        atol = ATOL_DEFAULT
    if rtol is None:
        rtol = RTOL_DEFAULT
    mat = np.array(mat)
    if mat.ndim != 2:
        return False
    return np.allclose(mat, np.conj(mat.T), rtol=rtol, atol=atol)

def is_positive_semidefinite_matrix(mat, rtol=RTOL_DEFAULT, atol=ATOL_DEFAULT):
    """Test if a matrix is positive semidefinite"""
    if atol is None:
        atol = ATOL_DEFAULT
    if rtol is None:
        rtol = RTOL_DEFAULT
    if not is_hermitian_matrix(mat, rtol=rtol, atol=atol):
        return False
    # Check eigenvalues are all positive
    vals = np.linalg.eigvalsh(mat)
    for v in vals:
        if v < -atol:
            return False
    return True

def is_identity_matrix(mat,
                       ignore_phase=False,
                       rtol=RTOL_DEFAULT,
                       atol=ATOL_DEFAULT):
    """Test if an array is an identity matrix."""
    if atol is None:
        atol = ATOL_DEFAULT
    if rtol is None:
        rtol = RTOL_DEFAULT
    mat = np.array(mat)
    if mat.ndim != 2:
        return False
    if ignore_phase:
        # If the matrix is equal to an identity up to a phase, we can
        # remove the phase by multiplying each entry by the complex
        # conjugate of the phase of the [0, 0] entry.
        theta = np.angle(mat[0, 0])
        mat = np.exp(-1j * theta) * mat
    # Check if square identity
    iden = np.eye(len(mat))
    return np.allclose(mat, iden, rtol=rtol, atol=atol)

def is_unitary_matrix(mat, rtol=RTOL_DEFAULT, atol=ATOL_DEFAULT):
    """Test if an array is a unitary matrix."""
    if atol is None:
        atol = ATOL_DEFAULT
    if rtol is None:
        rtol = RTOL_DEFAULT
    mat = np.array(mat)
    # Compute A^dagger.A and see if it is identity matrix
    mat = np.conj(mat.T).dot(mat)
    return is_identity_matrix(mat, ignore_phase=False, rtol=rtol, atol=atol)


# -*- 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 swaps in front of measurements by re-targeting the classical bit
 of the measure instruction.
"""

from qiskit.circuit import Measure
from qiskit.extensions.standard import SwapGate
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.dagcircuit import DAGCircuit

class OptimizeSwapBeforeMeasure(TransformationPass):
    """Remove the swaps followed by measurement (and adapt the measurement)"""

    def run(self, dag):
        """Return a new circuit that has been optimized."""
        swaps = dag.op_nodes(SwapGate)
        for swap in swaps:
            final_successor = []
            for successor in dag.successors(swap):
                final_successor.append(successor.type == 'out' or (successor.type == 'op' and
                                                                   successor.op.name == 'measure'))
            if all(final_successor):
                # the node swap needs to be removed and, if a measure follows, needs to be adapted
                swap_qargs = swap.qargs
                measure_layer = DAGCircuit()
                for qreg in dag.qregs.values():
                    measure_layer.add_qreg(qreg)
                for creg in dag.cregs.values():
                    measure_layer.add_creg(creg)
                for successor in dag.successors(swap):
                    if successor.type == 'op' and successor.op.name == 'measure':
                        # replace measure node with a new one, where qargs is set with the "other"
                        # swap qarg.
                        dag.remove_op_node(successor)
                        old_measure_qarg = successor.qargs[0]
                        new_measure_qarg = swap_qargs[swap_qargs.index(old_measure_qarg) - 1]
                        measure_layer.apply_operation_back(Measure(), [new_measure_qarg],
                                                           [successor.cargs[0]])
                dag.extend_back(measure_layer)
                dag.remove_op_node(swap)
        return dag


# -*- 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

"""Sampler strategy module for sampler functions.

Sampler functions have signature.
    ```python
    def sampler_function(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray:
        ...
    ```
where the supplied `continuous_pulse` is a function with signature:
    ```python
    def f(times: np.ndarray, *args, **kwargs) -> np.ndarray:
        ...
    ```
The sampler will call the `continuous_pulse` function with a set of times it will decide
according to the sampling strategy it implments along with the passed `args` and `kwargs`.
"""

from typing import Callable

import numpy as np

def left_sample(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray:
    """Left sample a continuous function.
    Args:
        continuous_pulse: Continuous pulse function to sample.
        duration: Duration to sample for.
        *args: Continuous pulse function args.
        **kwargs: Continuous pulse function kwargs.
    """
    times = np.arange(duration)
    return continuous_pulse(times, *args, **kwargs)

def right_sample(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray:
    """Sampling strategy for decorator.
    Args:
        continuous_pulse: Continuous pulse function to sample.
        duration: Duration to sample for.
        *args: Continuous pulse function args.
        **kwargs: Continuous pulse function kwargs.
    """
    times = np.arange(1, duration+1)
    return continuous_pulse(times, *args, **kwargs)

def midpoint_sample(continuous_pulse: Callable, duration: int, *args, **kwargs) -> np.ndarray:
    """Sampling strategy for decorator.
    Args:
        continuous_pulse: Continuous pulse function to sample.
        duration: Duration to sample for.
        *args: Continuous pulse function args.
        **kwargs: Continuous pulse function kwargs.
    """
    times = np.arange(1/2, duration + 1/2)
    return continuous_pulse(times, *args, **kwargs)


# -*- 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=unused-argument,too-many-return-statements,len-as-condition
"""
Transformations between QuantumChannel represenations.
"""

import numpy as np
import scipy.linalg as la

from qiskit.qiskiterror import QiskitError
from qiskit.quantum_info.operators.predicates import is_hermitian_matrix
from qiskit.quantum_info.operators.predicates import ATOL_DEFAULT

def _to_choi(rep, data, input_dim, output_dim):
    """Transform a QuantumChannel to the Choi representation."""
    if rep == 'Choi':
        return data
    if rep == 'Operator':
        return _from_operator('Choi', data, input_dim, output_dim)
    if rep == 'SuperOp':
        return _superop_to_choi(data, input_dim, output_dim)
    if rep == 'Kraus':
        return _kraus_to_choi(data, input_dim, output_dim)
    if rep == 'Chi':
        return _chi_to_choi(data, input_dim, output_dim)
    if rep == 'PTM':
        data = _ptm_to_superop(data, input_dim, output_dim)
        return _superop_to_choi(data, input_dim, output_dim)
    if rep == 'Stinespring':
        return _stinespring_to_choi(data, input_dim, output_dim)
    raise QiskitError('Invalid QuantumChannel {}'.format(rep))

def _to_superop(rep, data, input_dim, output_dim):
    """Transform a QuantumChannel to the SuperOp representation."""
    if rep == 'SuperOp':
        return data
    if rep == 'Operator':
        return _from_operator('SuperOp', data, input_dim, output_dim)
    if rep == 'Choi':
        return _choi_to_superop(data, input_dim, output_dim)
    if rep == 'Kraus':
        return _kraus_to_superop(data, input_dim, output_dim)
    if rep == 'Chi':
        data = _chi_to_choi(data, input_dim, output_dim)
        return _choi_to_superop(data, input_dim, output_dim)
    if rep == 'PTM':
        return _ptm_to_superop(data, input_dim, output_dim)
    if rep == 'Stinespring':
        return _stinespring_to_superop(data, input_dim, output_dim)
    raise QiskitError('Invalid QuantumChannel {}'.format(rep))

def _to_kraus(rep, data, input_dim, output_dim):
    """Transform a QuantumChannel to the Kraus representation."""
    if rep == 'Kraus':
        return data
    if rep == 'Stinespring':
        return _stinespring_to_kraus(data, input_dim, output_dim)
    if rep == 'Operator':
        return _from_operator('Kraus', data, input_dim, output_dim)
    # Convert via Choi and Kraus
    if rep != 'Choi':
        data = _to_choi(rep, data, input_dim, output_dim)
    return _choi_to_kraus(data, input_dim, output_dim)

def _to_chi(rep, data, input_dim, output_dim):
    """Transform a QuantumChannel to the Chi representation."""
    if rep == 'Chi':
        return data
    # Check valid n-qubit input
    _check_nqubit_dim(input_dim, output_dim)
    if rep == 'Operator':
        return _from_operator('Chi', data, input_dim, output_dim)
    # Convert via Choi representation
    if rep != 'Choi':
        data = _to_choi(rep, data, input_dim, output_dim)
    return _choi_to_chi(data, input_dim, output_dim)

def _to_ptm(rep, data, input_dim, output_dim):
    """Transform a QuantumChannel to the PTM representation."""
    if rep == 'PTM':
        return data
    # Check valid n-qubit input
    _check_nqubit_dim(input_dim, output_dim)
    if rep == 'Operator':
        return _from_operator('PTM', data, input_dim, output_dim)
    # Convert via Superoperator representation
    if rep != 'SuperOp':
        data = _to_superop(rep, data, input_dim, output_dim)
    return _superop_to_ptm(data, input_dim, output_dim)

def _to_stinespring(rep, data, input_dim, output_dim):
    """Transform a QuantumChannel to the Stinespring representation."""
    if rep == 'Stinespring':
        return data
    if rep == 'Operator':
        return _from_operator('Stinespring', data, input_dim, output_dim)
    # Convert via Superoperator representation
    if rep != 'Kraus':
        data = _to_kraus(rep, data, input_dim, output_dim)
    return _kraus_to_stinespring(data, input_dim, output_dim)

def _to_operator(rep, data, input_dim, output_dim):
    """Transform a QuantumChannel to the Operator representation."""
    if rep == 'Operator':
        return data
    if rep == 'Stinespring':
        return _stinespring_to_operator(data, input_dim, output_dim)
    # Convert via Kraus representation
    if rep != 'Kraus':
        data = _to_kraus(rep, data, input_dim, output_dim)
    return _kraus_to_operator(data, input_dim, output_dim)

def _from_operator(rep, data, input_dim, output_dim):
    """Transform Operator representation to other representation."""
    if rep == 'Operator':
        return data
    if rep == 'SuperOp':
        return np.kron(np.conj(data), data)
    if rep == 'Choi':
        vec = np.ravel(data, order='F')
        return np.outer(vec, np.conj(vec))
    if rep == 'Kraus':
        return ([data], None)
    if rep == 'Stinespring':
        return (data, None)
    if rep == 'Chi':
        _check_nqubit_dim(input_dim, output_dim)
        data = _from_operator('Choi', data, input_dim, output_dim)
        return _choi_to_chi(data, input_dim, output_dim)
    if rep == 'PTM':
        _check_nqubit_dim(input_dim, output_dim)
        data = _from_operator('SuperOp', data, input_dim, output_dim)
        return _superop_to_ptm(data, input_dim, output_dim)
    raise QiskitError('Invalid QuantumChannel {}'.format(rep))

def _kraus_to_operator(data, input_dim, output_dim):
    """Transform Kraus representation to Operator representation."""
    if data[1] is not None or len(data[0]) > 1:
        raise QiskitError(
            'Channel cannot be converted to Operator representation')
    return data[0][0]

def _stinespring_to_operator(data, input_dim, output_dim):
    """Transform Stinespring representation to Operator representation."""
    trace_dim = data[0].shape[0] // output_dim
    if data[1] is not None or trace_dim != 1:
        raise QiskitError(
            'Channel cannot be converted to Operator representation')
    return data[0]

def _superop_to_choi(data, input_dim, output_dim):
    """Transform SuperOp representation to Choi representation."""
    shape = (output_dim, output_dim, input_dim, input_dim)
    return _reshuffle(data, shape)

def _choi_to_superop(data, input_dim, output_dim):
    """Transform Choi to SuperOp representation."""
    shape = (input_dim, output_dim, input_dim, output_dim)
    return _reshuffle(data, shape)

def _kraus_to_choi(data, input_dim, output_dim):
    """Transform Kraus representation to Choi representation."""
    choi = 0
    kraus_l, kraus_r = data
    if kraus_r is None:
        for i in kraus_l:
            vec = i.ravel(order='F')
            choi += np.outer(vec, vec.conj())
    else:
        for i, j in zip(kraus_l, kraus_r):
            choi += np.outer(i.ravel(order='F'), j.ravel(order='F').conj())
    return choi

def _choi_to_kraus(data, input_dim, output_dim, atol=ATOL_DEFAULT):
    """Transform Choi representation to Kraus representation."""
    # Check if hermitian matrix
    if is_hermitian_matrix(data, atol=atol):
        # Get eigen-decomposition of Choi-matrix
        w, v = la.eigh(data)
        # Check eigenvaleus are non-negative
        if len(w[w < -atol]) == 0:
            # CP-map Kraus representation
            kraus = []
            for val, vec in zip(w, v.T):
                if abs(val) > atol:
                    k = np.sqrt(val) * vec.reshape(
                        (output_dim, input_dim), order='F')
                    kraus.append(k)
            # If we are converting a zero matrix, we need to return a Kraus set
            # with a single zero-element Kraus matrix
            if not kraus:
                kraus.append(np.zeros((output_dim, input_dim), dtype=complex))
            return (kraus, None)
    # Non-CP-map generalized Kraus representation
    mat_u, svals, mat_vh = la.svd(data)
    kraus_l = []
    kraus_r = []
    for val, vec_l, vec_r in zip(svals, mat_u.T, mat_vh.conj()):
        kraus_l.append(
            np.sqrt(val) * vec_l.reshape((output_dim, input_dim), order='F'))
        kraus_r.append(
            np.sqrt(val) * vec_r.reshape((output_dim, input_dim), order='F'))
    return (kraus_l, kraus_r)

def _stinespring_to_kraus(data, input_dim, output_dim):
    """Transform Stinespring representation to Kraus representation."""
    kraus_pair = []
    for stine in data:
        if stine is None:
            kraus_pair.append(None)
        else:
            trace_dim = stine.shape[0] // output_dim
            iden = np.eye(output_dim)
            kraus = []
            for j in range(trace_dim):
                vec = np.zeros(trace_dim)
                vec[j] = 1
                kraus.append(np.kron(iden, vec[None, :]).dot(stine))
            kraus_pair.append(kraus)
    return tuple(kraus_pair)

def _stinespring_to_choi(data, input_dim, output_dim):
    """Transform Stinespring representation to Choi representation."""
    trace_dim = data[0].shape[0] // output_dim
    stine_l = np.reshape(data[0], (output_dim, trace_dim, input_dim))
    if data[1] is None:
        stine_r = stine_l
    else:
        stine_r = np.reshape(data[1], (output_dim, trace_dim, input_dim))
    return np.reshape(
        np.einsum('iAj,kAl->jilk', stine_l, stine_r.conj()),
        2 * [input_dim * output_dim])

def _stinespring_to_superop(data, input_dim, output_dim):
    """Transform Stinespring representation to SuperOp representation."""
    trace_dim = data[0].shape[0] // output_dim
    stine_l = np.reshape(data[0], (output_dim, trace_dim, input_dim))
    if data[1] is None:
        stine_r = stine_l
    else:
        stine_r = np.reshape(data[1], (output_dim, trace_dim, input_dim))
    return np.reshape(
        np.einsum('iAj,kAl->ikjl', stine_r.conj(), stine_l),
        (output_dim * output_dim, input_dim * input_dim))

def _kraus_to_stinespring(data, input_dim, output_dim):
    """Transform Kraus representation to Stinespring representation."""
    stine_pair = [None, None]
    for i, kraus in enumerate(data):
        if kraus is not None:
            num_kraus = len(kraus)
            stine = np.zeros((output_dim * num_kraus, input_dim),
                             dtype=complex)
            for j, mat in enumerate(kraus):
                vec = np.zeros(num_kraus)
                vec[j] = 1
                stine += np.kron(mat, vec[:, None])
            stine_pair[i] = stine
    return tuple(stine_pair)

def _kraus_to_superop(data, input_dim, output_dim):
    """Transform Kraus representation to SuperOp representation."""
    kraus_l, kraus_r = data
    superop = 0
    if kraus_r is None:
        for i in kraus_l:
            superop += np.kron(np.conj(i), i)
    else:
        for i, j in zip(kraus_l, kraus_r):
            superop += np.kron(np.conj(j), i)
    return superop

def _chi_to_choi(data, input_dim, output_dim):
    """Transform Chi representation to a Choi representation."""
    num_qubits = int(np.log2(input_dim))
    return _transform_from_pauli(data, num_qubits)

def _choi_to_chi(data, input_dim, output_dim):
    """Transform Choi representation to the Chi representation."""
    num_qubits = int(np.log2(input_dim))
    return _transform_to_pauli(data, num_qubits)

def _ptm_to_superop(data, input_dim, output_dim):
    """Transform PTM representation to SuperOp representation."""
    num_qubits = int(np.log2(input_dim))
    return _transform_from_pauli(data, num_qubits)

def _superop_to_ptm(data, input_dim, output_dim):
    """Transform SuperOp representation to PTM representation."""
    num_qubits = int(np.log2(input_dim))
    return _transform_to_pauli(data, num_qubits)

def _bipartite_tensor(mat1, mat2, shape1=None, shape2=None):
    """Tensor product (A ⊗ B) to bipartite matrices and reravel indicies.

    This is used for tensor product of superoperators and Choi matrices.

    Args:
        mat1 (matrix_like): a bipartite matrix A
        mat2 (matrix_like): a bipartite matrix B
        shape1 (tuple): bipartite-shape for matrix A (a0, a1, a2, a3)
        shape2 (tuple): bipartite-shape for matrix B (b0, b1, b2, b3)

    Returns:
        np.array: a bipartite matrix for reravel(A ⊗ B).

    Raises:
        QiskitError: if input matrices are wrong shape.
    """
    # Convert inputs to numpy arrays
    mat1 = np.array(mat1)
    mat2 = np.array(mat2)

    # Determine bipartite dimensions if not provided
    dim_a0, dim_a1 = mat1.shape
    dim_b0, dim_b1 = mat2.shape
    if shape1 is None:
        sdim_a0 = int(np.sqrt(dim_a0))
        sdim_a1 = int(np.sqrt(dim_a1))
        shape1 = (sdim_a0, sdim_a0, sdim_a1, sdim_a1)
    if shape2 is None:
        sdim_b0 = int(np.sqrt(dim_b0))
        sdim_b1 = int(np.sqrt(dim_b1))
        shape2 = (sdim_b0, sdim_b0, sdim_b1, sdim_b1)
    # Check dimensions
    if len(shape1) != 4 or shape1[0] * shape1[1] != dim_a0 or \
            shape1[2] * shape1[3] != dim_a1:
        raise QiskitError("Invalid shape_a")
    if len(shape2) != 4 or shape2[0] * shape2[1] != dim_b0 or \
            shape2[2] * shape2[3] != dim_b1:
        raise QiskitError("Invalid shape_b")

    return _reravel(mat1, mat2, shape1, shape2)

def _reravel(mat1, mat2, shape1, shape2):
    """Reravel two bipartite matrices."""
    # Reshuffle indicies
    left_dims = shape1[:2] + shape2[:2]
    right_dims = shape1[2:] + shape2[2:]
    tensor_shape = left_dims + right_dims
    final_shape = (np.product(left_dims), np.product(right_dims))
    # Tensor product matrices
    data = np.kron(mat1, mat2)
    data = np.reshape(
        np.transpose(np.reshape(data, tensor_shape), (0, 2, 1, 3, 4, 6, 5, 7)),
        final_shape)
    return data

def _transform_to_pauli(data, num_qubits):
    """Change of basis of bipartite matrix represenation."""
    # Change basis: um_{i=0}^3 |i>><\sigma_i|
    basis_mat = np.array(
        [[1, 0, 0, 1], [0, 1, 1, 0], [0, -1j, 1j, 0], [1, 0j, 0, -1]],
        dtype=complex)
    # Note that we manually renormalized after change of basis
    # to avoid rounding errors from square-roots of 2.
    cob = basis_mat
    for _ in range(num_qubits - 1):
        dim = int(np.sqrt(len(cob)))
        cob = np.reshape(
            np.transpose(
                np.reshape(
                    np.kron(basis_mat, cob), (4, dim * dim, 2, 2, dim, dim)),
                (0, 1, 2, 4, 3, 5)), (4 * dim * dim, 4 * dim * dim))
    return np.dot(np.dot(cob, data), cob.conj().T) / 2**num_qubits

def _transform_from_pauli(data, num_qubits):
    """Change of basis of bipartite matrix represenation."""
    # Change basis: sum_{i=0}^3 =|\sigma_i>><i|
    basis_mat = np.array(
        [[1, 0, 0, 1], [0, 1, 1j, 0], [0, 1, -1j, 0], [1, 0j, 0, -1]],
        dtype=complex)
    # Note that we manually renormalized after change of basis
    # to avoid rounding errors from square-roots of 2.
    cob = basis_mat
    for _ in range(num_qubits - 1):
        dim = int(np.sqrt(len(cob)))
        cob = np.reshape(
            np.transpose(
                np.reshape(
                    np.kron(basis_mat, cob), (2, 2, dim, dim, 4, dim * dim)),
                (0, 2, 1, 3, 4, 5)), (4 * dim * dim, 4 * dim * dim))
    return np.dot(np.dot(cob, data), cob.conj().T) / 2**num_qubits

def _reshuffle(mat, shape):
    """Reshuffle the indicies of a bipartite matrix A[ij,kl] -> A[lj,ki]."""
    return np.reshape(
        np.transpose(np.reshape(mat, shape), (3, 1, 2, 0)),
        (shape[3] * shape[1], shape[0] * shape[2]))

def _check_nqubit_dim(input_dim, output_dim):
    """Return true if dims correspond to an n-qubit channel."""
    if input_dim != output_dim:
        raise QiskitError(
            'Not an n-qubit channel: input_dim' +
            ' ({}) != output_dim ({})'.format(input_dim, output_dim))
    num_qubits = int(np.log2(input_dim))
    if 2**num_qubits != input_dim:
        raise QiskitError('Not an n-qubit channel: input_dim != 2 ** n')


# -*- 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.

# This file is part of QuTiP: Quantum Toolbox in Python.
#
#    Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson.
#    All rights reserved.
#
#    Redistribution and use in source and binary forms, with or without
#    modification, are permitted provided that the following conditions are
#    met:
#
#    1. Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#
#    2. Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#
#    3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names
#       of its contributors may be used to endorse or promote products derived
#       from this software without specific prior written permission.
#
#    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
#    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
#    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
#    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
#    HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
###############################################################################

"""Bloch sphere"""

__all__ = ['Bloch']

import os
import numpy as np
import matplotlib.pyplot as plt  # pylint: disable=import-error
from matplotlib.patches import FancyArrowPatch  # pylint: disable=import-error
from mpl_toolkits.mplot3d import (Axes3D, proj3d)  # pylint: disable=import-error

class Arrow3D(FancyArrowPatch):
    """Makes a fancy arrow"""
    def __init__(self, xs, ys, zs, *args, **kwargs):
        FancyArrowPatch.__init__(self, (0, 0), (0, 0), *args, **kwargs)
        self._verts3d = xs, ys, zs

    def draw(self, renderer):
        xs3d, ys3d, zs3d = self._verts3d
        x_s, y_s, _ = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
        self.set_positions((x_s[0], y_s[0]), (x_s[1], y_s[1]))
        FancyArrowPatch.draw(self, renderer)

class Bloch():
    """Class for plotting data on the Bloch sphere.  Valid data can be
    either points, vectors, or qobj objects.

    Attributes:
        axes (instance):
            User supplied Matplotlib axes for Bloch sphere animation.
        fig (instance):
            User supplied Matplotlib Figure instance for plotting Bloch sphere.
        font_color (str):
            Color of font used for Bloch sphere labels.
        font_size (int):
            Size of font used for Bloch sphere labels.
        frame_alpha (float):
            Sets transparency of Bloch sphere frame.
        frame_color (str):
            Color of sphere wireframe.
        frame_width (int):
            Width of wireframe.
        point_color (list):
            List of colors for Bloch sphere point markers to cycle through.
            i.e. By default, points 0 and 4 will both be blue ('b').
        point_marker (list):
            List of point marker shapes to cycle through.
        point_size (list):
            List of point marker sizes. Note, not all point markers look
            the same size when plotted!
        sphere_alpha (float):
            Transparency of Bloch sphere itself.
        sphere_color (str):
            Color of Bloch sphere.
        figsize (list):
            Figure size of Bloch sphere plot.  Best to have both numbers the same;
            otherwise you will have a Bloch sphere that looks like a football.
        vector_color (list):
            List of vector colors to cycle through.
        vector_width (int):
            Width of displayed vectors.
        vector_style (str):
            Vector arrowhead style (from matplotlib's arrow style).
        vector_mutation (int):
            Width of vectors arrowhead.
        view (list):
            Azimuthal and Elevation viewing angles.
        xlabel (list):
            List of strings corresponding to +x and -x axes labels, respectively.
        xlpos (list):
            Positions of +x and -x labels respectively.
        ylabel (list):
            List of strings corresponding to +y and -y axes labels, respectively.
        ylpos (list):
            Positions of +y and -y labels respectively.
        zlabel (list):
            List of strings corresponding to +z and -z axes labels, respectively.
        zlpos (list):
            Positions of +z and -z labels respectively.
    """

    def __init__(self, fig=None, axes=None, view=None, figsize=None,
                 background=False):

        # Figure and axes
        self._ext_fig = False
        if fig is not None:
            self._ext_fig = True
        self.fig = fig
        self._ext_axes = False
        if axes is not None:
            self._ext_fig = True
            self._ext_axes = True
        self.axes = axes
        # Background axes, default = False
        self.background = background
        # The size of the figure in inches, default = [5,5].
        self.figsize = figsize if figsize else [5, 5]
        # Azimuthal and Elevation viewing angles, default = [-60,30].
        self.view = view if view else [-60, 30]
        # Color of Bloch sphere, default = #FFDDDD
        self.sphere_color = '#FFDDDD'
        # Transparency of Bloch sphere, default = 0.2
        self.sphere_alpha = 0.2
        # Color of wireframe, default = 'gray'
        self.frame_color = 'gray'
        # Width of wireframe, default = 1
        self.frame_width = 1
        # Transparency of wireframe, default = 0.2
        self.frame_alpha = 0.2
        # Labels for x-axis (in LaTex), default = ['$x$', '']
        self.xlabel = ['$x$', '']
        # Position of x-axis labels, default = [1.2, -1.2]
        self.xlpos = [1.2, -1.2]
        # Labels for y-axis (in LaTex), default = ['$y$', '']
        self.ylabel = ['$y$', '']
        # Position of y-axis labels, default = [1.1, -1.1]
        self.ylpos = [1.2, -1.2]
        # Labels for z-axis (in LaTex),
        # default = [r'$\left|1\right>$', r'$\left|0\right>$']
        self.zlabel = [r'$\left|0\right>$', r'$\left|1\right>$']
        # Position of z-axis labels, default = [1.2, -1.2]
        self.zlpos = [1.2, -1.2]
        # ---font options---
        # Color of fonts, default = 'black'
        self.font_color = 'black'
        # Size of fonts, default = 20
        self.font_size = 20

        # ---vector options---
        # List of colors for Bloch vectors, default = ['b','g','r','y']
        self.vector_color = ['#dc267f', '#648fff', '#fe6100', '#785ef0',
                             '#ffb000']
        #: Width of Bloch vectors, default = 5
        self.vector_width = 5
        #: Style of Bloch vectors, default = '-|>' (or 'simple')
        self.vector_style = '-|>'
        #: Sets the width of the vectors arrowhead
        self.vector_mutation = 20

        # ---point options---
        # List of colors for Bloch point markers, default = ['b','g','r','y']
        self.point_color = ['b', 'r', 'g', '#CC6600']
        # Size of point markers, default = 25
        self.point_size = [25, 32, 35, 45]
        # Shape of point markers, default = ['o','^','d','s']
        self.point_marker = ['o', 's', 'd', '^']

        # ---data lists---
        # Data for point markers
        self.points = []
        # Data for Bloch vectors
        self.vectors = []
        # Data for annotations
        self.annotations = []
        # Number of times sphere has been saved
        self.savenum = 0
        # Style of points, 'm' for multiple colors, 's' for single color
        self.point_style = []

        # status of rendering
        self._rendered = False

    def set_label_convention(self, convention):
        """Set x, y and z labels according to one of conventions.

        Args:
            convention (str):
                One of the following:
                    - "original"
                    - "xyz"
                    - "sx sy sz"
                    - "01"
                    - "polarization jones"
                    - "polarization jones letters"
                    see also: http://en.wikipedia.org/wiki/Jones_calculus
                    - "polarization stokes"
                    see also: http://en.wikipedia.org/wiki/Stokes_parameters
        Raises:
            Exception: If convention is not valid.
        """
        ketex = "$\\left.|%s\\right\\rangle$"
        # \left.| is on purpose, so that every ket has the same size

        if convention == "original":
            self.xlabel = ['$x$', '']
            self.ylabel = ['$y$', '']
            self.zlabel = ['$\\left|0\\right>$', '$\\left|1\\right>$']
        elif convention == "xyz":
            self.xlabel = ['$x$', '']
            self.ylabel = ['$y$', '']
            self.zlabel = ['$z$', '']
        elif convention == "sx sy sz":
            self.xlabel = ['$s_x$', '']
            self.ylabel = ['$s_y$', '']
            self.zlabel = ['$s_z$', '']
        elif convention == "01":
            self.xlabel = ['', '']
            self.ylabel = ['', '']
            self.zlabel = ['$\\left|0\\right>$', '$\\left|1\\right>$']
        elif convention == "polarization jones":
            self.xlabel = [ketex % "\\nearrow\\hspace{-1.46}\\swarrow",
                           ketex % "\\nwarrow\\hspace{-1.46}\\searrow"]
            self.ylabel = [ketex % "\\circlearrowleft", ketex %
                           "\\circlearrowright"]
            self.zlabel = [ketex % "\\leftrightarrow", ketex % "\\updownarrow"]
        elif convention == "polarization jones letters":
            self.xlabel = [ketex % "D", ketex % "A"]
            self.ylabel = [ketex % "L", ketex % "R"]
            self.zlabel = [ketex % "H", ketex % "V"]
        elif convention == "polarization stokes":
            self.ylabel = ["$\\nearrow\\hspace{-1.46}\\swarrow$",
                           "$\\nwarrow\\hspace{-1.46}\\searrow$"]
            self.zlabel = ["$\\circlearrowleft$", "$\\circlearrowright$"]
            self.xlabel = ["$\\leftrightarrow$", "$\\updownarrow$"]
        else:
            raise Exception("No such convention.")

    def __str__(self):
        string = ""
        string += "Bloch data:\n"
        string += "-----------\n"
        string += "Number of points:  " + str(len(self.points)) + "\n"
        string += "Number of vectors: " + str(len(self.vectors)) + "\n"
        string += "\n"
        string += "Bloch sphere properties:\n"
        string += "------------------------\n"
        string += "font_color:      " + str(self.font_color) + "\n"
        string += "font_size:       " + str(self.font_size) + "\n"
        string += "frame_alpha:     " + str(self.frame_alpha) + "\n"
        string += "frame_color:     " + str(self.frame_color) + "\n"
        string += "frame_width:     " + str(self.frame_width) + "\n"
        string += "point_color:     " + str(self.point_color) + "\n"
        string += "point_marker:    " + str(self.point_marker) + "\n"
        string += "point_size:      " + str(self.point_size) + "\n"
        string += "sphere_alpha:    " + str(self.sphere_alpha) + "\n"
        string += "sphere_color:    " + str(self.sphere_color) + "\n"
        string += "figsize:         " + str(self.figsize) + "\n"
        string += "vector_color:    " + str(self.vector_color) + "\n"
        string += "vector_width:    " + str(self.vector_width) + "\n"
        string += "vector_style:    " + str(self.vector_style) + "\n"
        string += "vector_mutation: " + str(self.vector_mutation) + "\n"
        string += "view:            " + str(self.view) + "\n"
        string += "xlabel:          " + str(self.xlabel) + "\n"
        string += "xlpos:           " + str(self.xlpos) + "\n"
        string += "ylabel:          " + str(self.ylabel) + "\n"
        string += "ylpos:           " + str(self.ylpos) + "\n"
        string += "zlabel:          " + str(self.zlabel) + "\n"
        string += "zlpos:           " + str(self.zlpos) + "\n"
        return string

    def clear(self):
        """Resets Bloch sphere data sets to empty.
        """
        self.points = []
        self.vectors = []
        self.point_style = []
        self.annotations = []

    def add_points(self, points, meth='s'):
        """Add a list of data points to bloch sphere.
        Args:
            points (array_like):
                Collection of data points.
            meth (str):
                Type of points to plot, use 'm' for multicolored, 'l' for points
                connected with a line.
        """
        if not isinstance(points[0], (list, np.ndarray)):
            points = [[points[0]], [points[1]], [points[2]]]
        points = np.array(points)
        if meth == 's':
            if len(points[0]) == 1:
                pnts = np.array([[points[0][0]],
                                 [points[1][0]], [points[2][0]]])
                pnts = np.append(pnts, points, axis=1)
            else:
                pnts = points
            self.points.append(pnts)
            self.point_style.append('s')
        elif meth == 'l':
            self.points.append(points)
            self.point_style.append('l')
        else:
            self.points.append(points)
            self.point_style.append('m')

    def add_vectors(self, vectors):
        """Add a list of vectors to Bloch sphere.

        Args:
            vectors (array_like):
                Array with vectors of unit length or smaller.
        """
        if isinstance(vectors[0], (list, np.ndarray)):
            for vec in vectors:
                self.vectors.append(vec)
        else:
            self.vectors.append(vectors)

    def add_annotation(self, state_or_vector, text, **kwargs):
        """Add a text or LaTeX annotation to Bloch sphere,
        parametrized by a qubit state or a vector.

        Args:
            state_or_vector (array_like):
                Position for the annotation.
                Qobj of a qubit or a vector of 3 elements.
            text (str):
                Annotation text.
                You can use LaTeX, but remember to use raw string
                e.g. r"$\\langle x \\rangle$"
                or escape backslashes
                e.g. "$\\\\langle x \\\\rangle$".
            **kwargs:
                Options as for mplot3d.axes3d.text, including:
                fontsize, color, horizontalalignment, verticalalignment.
        Raises:
            Exception: If input not array_like or tuple.
        """
        if isinstance(state_or_vector, (list, np.ndarray, tuple)) \
                and len(state_or_vector) == 3:
            vec = state_or_vector
        else:
            raise Exception("Position needs to be specified by a qubit " +
                            "state or a 3D vector.")
        self.annotations.append({'position': vec,
                                 'text': text,
                                 'opts': kwargs})

    def make_sphere(self):
        """
        Plots Bloch sphere and data sets.
        """
        self.render()

    def render(self, title=''):
        """
        Render the Bloch sphere and its data sets in on given figure and axes.
        """
        if self._rendered:
            self.axes.clear()

        self._rendered = True

        # Figure instance for Bloch sphere plot
        if not self._ext_fig:
            self.fig = plt.figure(figsize=self.figsize)

        if not self._ext_axes:
            self.axes = Axes3D(self.fig, azim=self.view[0], elev=self.view[1])

        if self.background:
            self.axes.clear()
            self.axes.set_xlim3d(-1.3, 1.3)
            self.axes.set_ylim3d(-1.3, 1.3)
            self.axes.set_zlim3d(-1.3, 1.3)
        else:
            self.plot_axes()
            self.axes.set_axis_off()
            self.axes.set_xlim3d(-0.7, 0.7)
            self.axes.set_ylim3d(-0.7, 0.7)
            self.axes.set_zlim3d(-0.7, 0.7)

        self.axes.grid(False)
        self.plot_back()
        self.plot_points()
        self.plot_vectors()
        self.plot_front()
        self.plot_axes_labels()
        self.plot_annotations()
        self.axes.set_title(title, fontsize=self.font_size, y=1.08)

    def plot_back(self):
        """back half of sphere"""
        u_angle = np.linspace(0, np.pi, 25)
        v_angle = np.linspace(0, np.pi, 25)
        x_dir = np.outer(np.cos(u_angle), np.sin(v_angle))
        y_dir = np.outer(np.sin(u_angle), np.sin(v_angle))
        z_dir = np.outer(np.ones(u_angle.shape[0]), np.cos(v_angle))
        self.axes.plot_surface(x_dir, y_dir, z_dir, rstride=2, cstride=2,
                               color=self.sphere_color, linewidth=0,
                               alpha=self.sphere_alpha)
        # wireframe
        self.axes.plot_wireframe(x_dir, y_dir, z_dir, rstride=5, cstride=5,
                                 color=self.frame_color,
                                 alpha=self.frame_alpha)
        # equator
        self.axes.plot(1.0 * np.cos(u_angle), 1.0 * np.sin(u_angle), zs=0, zdir='z',
                       lw=self.frame_width, color=self.frame_color)
        self.axes.plot(1.0 * np.cos(u_angle), 1.0 * np.sin(u_angle), zs=0, zdir='x',
                       lw=self.frame_width, color=self.frame_color)

    def plot_front(self):
        """front half of sphere"""
        u_angle = np.linspace(-np.pi, 0, 25)
        v_angle = np.linspace(0, np.pi, 25)
        x_dir = np.outer(np.cos(u_angle), np.sin(v_angle))
        y_dir = np.outer(np.sin(u_angle), np.sin(v_angle))
        z_dir = np.outer(np.ones(u_angle.shape[0]), np.cos(v_angle))
        self.axes.plot_surface(x_dir, y_dir, z_dir, rstride=2, cstride=2,
                               color=self.sphere_color, linewidth=0,
                               alpha=self.sphere_alpha)
        # wireframe
        self.axes.plot_wireframe(x_dir, y_dir, z_dir, rstride=5, cstride=5,
                                 color=self.frame_color,
                                 alpha=self.frame_alpha)
        # equator
        self.axes.plot(1.0 * np.cos(u_angle), 1.0 * np.sin(u_angle),
                       zs=0, zdir='z', lw=self.frame_width,
                       color=self.frame_color)
        self.axes.plot(1.0 * np.cos(u_angle), 1.0 * np.sin(u_angle),
                       zs=0, zdir='x', lw=self.frame_width,
                       color=self.frame_color)

    def plot_axes(self):
        """axes"""
        span = np.linspace(-1.0, 1.0, 2)
        self.axes.plot(span, 0 * span, zs=0, zdir='z', label='X',
                       lw=self.frame_width, color=self.frame_color)
        self.axes.plot(0 * span, span, zs=0, zdir='z', label='Y',
                       lw=self.frame_width, color=self.frame_color)
        self.axes.plot(0 * span, span, zs=0, zdir='y', label='Z',
                       lw=self.frame_width, color=self.frame_color)

    def plot_axes_labels(self):
        """axes labels"""
        opts = {'fontsize': self.font_size,
                'color': self.font_color,
                'horizontalalignment': 'center',
                'verticalalignment': 'center'}
        self.axes.text(0, -self.xlpos[0], 0, self.xlabel[0], **opts)
        self.axes.text(0, -self.xlpos[1], 0, self.xlabel[1], **opts)

        self.axes.text(self.ylpos[0], 0, 0, self.ylabel[0], **opts)
        self.axes.text(self.ylpos[1], 0, 0, self.ylabel[1], **opts)

        self.axes.text(0, 0, self.zlpos[0], self.zlabel[0], **opts)
        self.axes.text(0, 0, self.zlpos[1], self.zlabel[1], **opts)

        for item in (self.axes.w_xaxis.get_ticklines() +
                     self.axes.w_xaxis.get_ticklabels()):
            item.set_visible(False)
        for item in (self.axes.w_yaxis.get_ticklines() +
                     self.axes.w_yaxis.get_ticklabels()):
            item.set_visible(False)
        for item in (self.axes.w_zaxis.get_ticklines() +
                     self.axes.w_zaxis.get_ticklabels()):
            item.set_visible(False)

    def plot_vectors(self):
        """Plot vector"""
        # -X and Y data are switched for plotting purposes
        for k in range(len(self.vectors)):

            xs3d = self.vectors[k][1] * np.array([0, 1])
            ys3d = -self.vectors[k][0] * np.array([0, 1])
            zs3d = self.vectors[k][2] * np.array([0, 1])

            color = self.vector_color[np.mod(k, len(self.vector_color))]

            if self.vector_style == '':
                # simple line style
                self.axes.plot(xs3d, ys3d, zs3d,
                               zs=0, zdir='z', label='Z',
                               lw=self.vector_width, color=color)
            else:
                # decorated style, with arrow heads
                arr = Arrow3D(xs3d, ys3d, zs3d,
                              mutation_scale=self.vector_mutation,
                              lw=self.vector_width,
                              arrowstyle=self.vector_style,
                              color=color)

                self.axes.add_artist(arr)

    def plot_points(self):
        """Plot points"""
        # -X and Y data are switched for plotting purposes
        for k in range(len(self.points)):
            num = len(self.points[k][0])
            dist = [np.sqrt(self.points[k][0][j] ** 2 +
                            self.points[k][1][j] ** 2 +
                            self.points[k][2][j] ** 2) for j in range(num)]
            if any(abs(dist - dist[0]) / dist[0] > 1e-12):
                # combine arrays so that they can be sorted together
                zipped = list(zip(dist, range(num)))
                zipped.sort()  # sort rates from lowest to highest
                dist, indperm = zip(*zipped)
                indperm = np.array(indperm)
            else:
                indperm = np.arange(num)
            if self.point_style[k] == 's':
                self.axes.scatter(
                    np.real(self.points[k][1][indperm]),
                    - np.real(self.points[k][0][indperm]),
                    np.real(self.points[k][2][indperm]),
                    s=self.point_size[np.mod(k, len(self.point_size))],
                    alpha=1,
                    edgecolor='none',
                    zdir='z',
                    color=self.point_color[np.mod(k, len(self.point_color))],
                    marker=self.point_marker[np.mod(k,
                                                    len(self.point_marker))])

            elif self.point_style[k] == 'm':
                pnt_colors = np.array(self.point_color *
                                      int(np.ceil(num /
                                                  float(len(self.point_color)))))

                pnt_colors = pnt_colors[0:num]
                pnt_colors = list(pnt_colors[indperm])
                marker = self.point_marker[np.mod(k, len(self.point_marker))]
                pnt_size = self.point_size[np.mod(k, len(self.point_size))]
                self.axes.scatter(np.real(self.points[k][1][indperm]),
                                  -np.real(self.points[k][0][indperm]),
                                  np.real(self.points[k][2][indperm]),
                                  s=pnt_size, alpha=1, edgecolor='none',
                                  zdir='z', color=pnt_colors,
                                  marker=marker)

            elif self.point_style[k] == 'l':
                color = self.point_color[np.mod(k, len(self.point_color))]
                self.axes.plot(np.real(self.points[k][1]),
                               -np.real(self.points[k][0]),
                               np.real(self.points[k][2]),
                               alpha=0.75, zdir='z',
                               color=color)

    def plot_annotations(self):
        """Plot annotations"""
        # -X and Y data are switched for plotting purposes
        for annotation in self.annotations:
            vec = annotation['position']
            opts = {'fontsize': self.font_size,
                    'color': self.font_color,
                    'horizontalalignment': 'center',
                    'verticalalignment': 'center'}
            opts.update(annotation['opts'])
            self.axes.text(vec[1], -vec[0], vec[2],
                           annotation['text'], **opts)

    def show(self, title=''):
        """
        Display Bloch sphere and corresponding data sets.
        """
        self.render(title=title)
        if self.fig:
            plt.show(self.fig)

    def save(self, name=None, output='png', dirc=None):
        """Saves Bloch sphere to file of type ``format`` in directory ``dirc``.
        Args:
            name (str):
                Name of saved image. Must include path and format as well.
                i.e. '/Users/Paul/Desktop/bloch.png'
                This overrides the 'format' and 'dirc' arguments.
            output (str):
                Format of output image.
            dirc (str):
                Directory for output images. Defaults to current working directory.
        """

        self.render()
        if dirc:
            if not os.path.isdir(os.getcwd() + "/" + str(dirc)):
                os.makedirs(os.getcwd() + "/" + str(dirc))
        if name is None:
            if dirc:
                self.fig.savefig(os.getcwd() + "/" + str(dirc) + '/bloch_' +
                                 str(self.savenum) + '.' + output)
            else:
                self.fig.savefig(os.getcwd() + '/bloch_' + str(self.savenum) +
                                 '.' + output)
        else:
            self.fig.savefig(name)
        self.savenum += 1
        if self.fig:
            plt.close(self.fig)

def _hide_tick_lines_and_labels(axis):
    """
    Set visible property of ticklines and ticklabels of an axis to False
    """
    for item in axis.get_ticklines() + axis.get_ticklabels():
        item.set_visible(False)


# -*- 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,invalid-sequence-index,unused-argument
# pylint: disable=unsupported-assignment-operation

"""
Methods to assist with compiling tasks.
"""
import warnings

from qiskit.quantum_info import synthesis

def euler_angles_1q(unitary_matrix):
    """Deprecated after 0.8
    """
    warnings.warn("euler_angles_1q function is now accessible under "
                  "qiskit.quantum_info.synthesis", DeprecationWarning)
    return synthesis.euler_angles_1q(unitary_matrix)

def two_qubit_kak(unitary_matrix, verify_gate_sequence=False):
    """Deprecated after 0.8
    """
    warnings.warn("two_qubit_kak function is now accessible under "
                  "qiskit.quantum_info.synthesis", DeprecationWarning)
    return synthesis.two_qubit_kak(unitary_matrix)


# -*- 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 drawing circuits in ascii art or some other text representation
"""

from shutil import get_terminal_size
import sys
import sympy
from numpy import ndarray

from .exceptions import VisualizationError

class DrawElement():
    """ An element is an instruction or an operation that need to be drawn."""

    def __init__(self, label=None):
        self._width = None
        self.label = self.mid_content = label
        self.top_format = self.mid_format = self.bot_format = "%s"
        self.top_connect = self.bot_connect = " "
        self.top_pad = self._mid_padding = self.bot_pad = " "
        self.mid_bck = self.top_bck = self.bot_bck = " "
        self.bot_connector = {}
        self.top_connector = {}
        self.right_fill = self.left_fill = self.layer_width = 0
        self.wire_label = ""

    @property
    def top(self):
        """ Constructs the top line of the element"""
        ret = self.top_format % self.top_connect.center(
            self.width, self.top_pad)
        if self.right_fill:
            ret = ret.ljust(self.right_fill, self.top_pad)
        if self.left_fill:
            ret = ret.rjust(self.left_fill, self.top_pad)
        ret = ret.center(self.layer_width, self.top_bck)
        return ret

    @property
    def mid(self):
        """ Constructs the middle line of the element"""
        ret = self.mid_format % self.mid_content.center(
            self.width, self._mid_padding)
        if self.right_fill:
            ret = ret.ljust(self.right_fill, self._mid_padding)
        if self.left_fill:
            ret = ret.rjust(self.left_fill, self._mid_padding)
        ret = ret.center(self.layer_width, self.mid_bck)
        return ret

    @property
    def bot(self):
        """ Constructs the bottom line of the element"""
        ret = self.bot_format % self.bot_connect.center(
            self.width, self.bot_pad)
        if self.right_fill:
            ret = ret.ljust(self.right_fill, self.bot_pad)
        if self.left_fill:
            ret = ret.rjust(self.left_fill, self.bot_pad)
        ret = ret.center(self.layer_width, self.bot_bck)
        return ret

    @property
    def length(self):
        """ Returns the length of the element, including the box around."""
        return max(len(self.top), len(self.mid), len(self.bot))

    @property
    def width(self):
        """ Returns the width of the label, including padding"""
        if self._width:
            return self._width
        return len(self.mid_content)

    @width.setter
    def width(self, value):
        self._width = value

    def connect(self, wire_char, where, label=None):
        """
        Connects boxes and elements using wire_char and setting proper connectors.
        Args:
            wire_char (char): For example '║' or '│'.
            where (list["top", "bot"]): Where the connector should be set.
            label (string): Some connectors have a label (see cu1, for example).
        """

        if 'top' in where and self.top_connector:
            self.top_connect = self.top_connector[wire_char]

        if 'bot' in where and self.bot_connector:
            self.bot_connect = self.bot_connector[wire_char]

        if label:
            self.top_format = self.top_format[:-1] + (label if label else "")

class BoxOnClWire(DrawElement):
    """ Draws a box on the classical wire
        top: ┌───┐   ┌───┐
        mid: ╡ A ╞ ══╡ A ╞══
        bot: └───┘   └───┘
    """

    def __init__(self, label="", top_connect='─', bot_connect='─'):
        super().__init__(label)
        self.top_format = "┌─%s─┐"
        self.mid_format = "╡ %s ╞"
        self.bot_format = "└─%s─┘"
        self.top_pad = self.bot_pad = '─'
        self.mid_bck = '═'
        self.top_connect = top_connect
        self.bot_connect = bot_connect
        self.mid_content = label

class BoxOnQuWire(DrawElement):
    """ Draws a box on the quantum wire
        top: ┌───┐   ┌───┐
        mid: ┤ A ├ ──┤ A ├──
        bot: └───┘   └───┘
    """

    def __init__(self, label="", top_connect='─', bot_connect='─'):
        super().__init__(label)
        self.top_format = "┌─%s─┐"
        self.mid_format = "┤ %s ├"
        self.bot_format = "└─%s─┘"
        self.top_pad = self.bot_pad = self.mid_bck = '─'
        self.top_connect = top_connect
        self.bot_connect = bot_connect
        self.mid_content = label
        self.top_connector = {"│": '┴'}
        self.bot_connector = {"│": '┬'}

class MeasureTo(DrawElement):
    """ The element on the classic wire to which the measure is performed
    top:  ║     ║
    mid: ═╩═ ═══╩═══
    bot:
    """

    def __init__(self):
        super().__init__()
        self.top_connect = " ║ "
        self.mid_content = "═╩═"
        self.bot_connect = "   "
        self.mid_bck = "═"

class MeasureFrom(BoxOnQuWire):
    """ The element on the quantum wire in which the measure is performed
        top: ┌─┐    ┌─┐
        mid: ┤M├ ───┤M├───
        bot: └╥┘    └╥┘
    """

    def __init__(self):
        super().__init__()
        self.top_format = self.mid_format = self.bot_format = "%s"
        self.top_connect = "┌─┐"
        self.mid_content = "┤M├"
        self.bot_connect = "└╥┘"

        self.top_pad = self.bot_pad = " "
        self._mid_padding = '─'

class MultiBox(DrawElement):
    """Elements that is draw on over multiple wires."""

    def center_label(self, input_length, order):
        """
        In multi-bit elements, the label is centered vertically.
        Args:
            input_length (int): Rhe amount of wires affected.
            order (int): Which middle element is this one?
        """
        location_in_the_box = '*'.center(input_length * 2 - 1).index('*') + 1
        top_limit = order * 2 + 2
        bot_limit = top_limit + 2
        if top_limit <= location_in_the_box < bot_limit:
            if location_in_the_box == top_limit:
                self.top_connect = self.label
            elif location_in_the_box == top_limit + 1:
                self.mid_content = self.label
            else:
                self.bot_connect = self.label

    @property
    def width(self):
        """ Returns the width of the label, including padding"""
        if self._width:
            return self._width
        return len(self.label)

class BoxOnQuWireTop(MultiBox, BoxOnQuWire):
    """ Draws the top part of a box that affects more than one quantum wire"""

    def __init__(self, label="", top_connect=None, wire_label=''):
        super().__init__(label)
        self.wire_label = wire_label
        self.bot_connect = self.bot_pad = " "
        self.mid_content = ""  # The label will be put by some other part of the box.
        self.left_fill = len(self.wire_label)
        self.top_format = "┌{}─%s─┐".format(self.top_pad * self.left_fill)
        self.mid_format = "┤{} %s ├".format(self.wire_label)
        self.bot_format = "│{} %s │".format(self.bot_pad * self.left_fill)
        self.top_connect = top_connect if top_connect else '─'

class BoxOnQuWireMid(MultiBox, BoxOnQuWire):
    """ Draws the middle part of a box that affects more than one quantum wire"""

    def __init__(self, label, input_length, order, wire_label=''):
        super().__init__(label)
        self.top_pad = self.bot_pad = self.top_connect = self.bot_connect = " "
        self.wire_label = wire_label
        self.left_fill = len(self.wire_label)
        self.top_format = "│{} %s │".format(self.top_pad * self.left_fill)
        self.mid_format = "┤{} %s ├".format(self.wire_label)
        self.bot_format = "│{} %s │".format(self.bot_pad * self.left_fill)
        self.top_connect = self.bot_connect = self.mid_content = ''
        self.center_label(input_length, order)

class BoxOnQuWireBot(MultiBox, BoxOnQuWire):
    """ Draws the bottom part of a box that affects more than one quantum wire"""

    def __init__(self, label, input_length, bot_connect='─', wire_label=''):
        super().__init__(label)
        self.wire_label = wire_label
        self.top_pad = " "
        self.left_fill = len(self.wire_label)
        self.top_format = "│{} %s │".format(self.top_pad * self.left_fill)
        self.mid_format = "┤{} %s ├".format(self.wire_label)
        self.bot_format = "└{}─%s─┘".format(self.bot_pad * self.left_fill)
        self.bot_connect = bot_connect

        self.mid_content = self.top_connect = ""
        if input_length <= 2:
            self.top_connect = label

class BoxOnClWireTop(MultiBox, BoxOnClWire):
    """ Draws the top part of a conditional box that affects more than one classical wire"""

    def __init__(self, label="", top_connect=None, wire_label=''):
        super().__init__(label)
        self.wire_label = wire_label
        self.mid_content = ""  # The label will be put by some other part of the box.
        self.bot_format = "│ %s │"
        self.top_connect = top_connect if top_connect else '─'
        self.bot_connect = self.bot_pad = " "

class BoxOnClWireMid(MultiBox, BoxOnClWire):
    """ Draws the middle part of a conditional box that affects more than one classical wire"""

    def __init__(self, label, input_length, order, wire_label=''):
        super().__init__(label)
        self.wire_label = wire_label
        self.top_format = "│ %s │"
        self.bot_format = "│ %s │"
        self.top_pad = self.bot_pad = ' '
        self.top_connect = self.bot_connect = self.mid_content = ''
        self.center_label(input_length, order)

class BoxOnClWireBot(MultiBox, BoxOnClWire):
    """ Draws the bottom part of a conditional box that affects more than one classical wire"""

    def __init__(self, label, input_length, bot_connect='─', wire_label=''):
        super().__init__(label)
        self.wire_label = wire_label
        self.top_format = "│ %s │"
        self.top_pad = " "
        self.bot_connect = bot_connect

        self.mid_content = self.top_connect = ""
        if input_length <= 2:
            self.top_connect = label

class DirectOnQuWire(DrawElement):
    """
    Element to the wire (without the box).
    """

    def __init__(self, label=""):
        super().__init__(label)
        self.top_format = ' %s '
        self.mid_format = '─%s─'
        self.bot_format = ' %s '
        self._mid_padding = '─'
        self.top_connector = {"│": '│'}
        self.bot_connector = {"│": '│'}

class Barrier(DirectOnQuWire):
    """ Draws a barrier.
        top:  ░     ░
        mid: ─░─ ───░───
        bot:  ░     ░
    """

    def __init__(self, label=""):
        super().__init__("░")
        self.top_connect = "░"
        self.bot_connect = "░"
        self.top_connector = {}
        self.bot_connector = {}

class Ex(DirectOnQuWire):
    """ Draws an X (usually with a connector). E.g. the top part of a swap gate
    top:
    mid: ─X─ ───X───
    bot:  │     │
    """

    def __init__(self, bot_connect=" ", top_connect=" "):
        super().__init__("X")
        self.bot_connect = bot_connect
        self.top_connect = top_connect

class Reset(DirectOnQuWire):
    """ Draws a reset gate"""

    def __init__(self):
        super().__init__("|0>")

class Bullet(DirectOnQuWire):
    """ Draws a bullet (usually with a connector). E.g. the top part of a CX gate.
    top:
    mid: ─■─  ───■───
    bot:  │      │
    """

    def __init__(self, top_connect="", bot_connect=""):
        super().__init__('■')
        self.top_connect = top_connect
        self.bot_connect = bot_connect
        self.mid_bck = '─'

class EmptyWire(DrawElement):
    """ This element is just the wire, with no instructions nor operations."""

    def __init__(self, wire):
        super().__init__(wire)
        self._mid_padding = self.mid_bck = wire

    @staticmethod
    def fillup_layer(layer, first_clbit):
        """
        Given a layer, replace the Nones in it with EmptyWire elements.
        Args:
            layer (list): The layer that contains Nones.
            first_clbit (int): The first wire that is classic.

        Returns:
            list: The new layer, with no Nones.
        """
        for nones in [i for i, x in enumerate(layer) if x is None]:
            layer[nones] = EmptyWire('═') if nones >= first_clbit else EmptyWire('─')
        return layer

class BreakWire(DrawElement):
    """ This element is used to break the drawing in several pages."""

    def __init__(self, arrow_char):
        super().__init__()
        self.top_format = self.mid_format = self.bot_format = "%s"
        self.top_connect = arrow_char
        self.mid_content = arrow_char
        self.bot_connect = arrow_char

    @staticmethod
    def fillup_layer(layer_length, arrow_char):
        """
        Creates a layer with BreakWire elements.
        Args:
            layer_length (int): The length of the layer to create
            arrow_char (char): The char used to create the BreakWire element.

        Returns:
            list: The new layer.
        """
        breakwire_layer = []
        for _ in range(layer_length):
            breakwire_layer.append(BreakWire(arrow_char))
        return breakwire_layer

class InputWire(DrawElement):
    """ This element is the label and the initial value of a wire."""

    def __init__(self, label):
        super().__init__(label)

    @staticmethod
    def fillup_layer(names):  # pylint: disable=arguments-differ
        """
        Creates a layer with InputWire elements.
        Args:
            names (list): List of names for the wires.

        Returns:
            list: The new layer
        """
        longest = max([len(name) for name in names])
        inputs_wires = []
        for name in names:
            inputs_wires.append(InputWire(name.rjust(longest)))
        return inputs_wires

class TextDrawing():
    """ The text drawing"""

    def __init__(self, qregs, cregs, instructions, plotbarriers=True,
                 line_length=None, vertically_compressed=True):
        self.qregs = qregs
        self.cregs = cregs
        self.instructions = instructions

        self.plotbarriers = plotbarriers
        self.line_length = line_length
        self.vertically_compressed = vertically_compressed

    def __str__(self):
        return self.single_string()

    def _repr_html_(self):
        return '
%s
' % self.single_string()

    def _get_qubit_labels(self):
        qubits = []
        for qubit in self.qregs:
            qubits.append("%s_%s" % (qubit[0].name, qubit[1]))
        return qubits

    def _get_clbit_labels(self):
        clbits = []
        for clbit in self.cregs:
            clbits.append("%s_%s" % (clbit[0].name, clbit[1]))
        return clbits

    def single_string(self):
        """
        Creates a long string with the ascii art
        Returns:
            str: The lines joined by '\n'
        """
        return "\n".join(self.lines())

    def dump(self, filename, encoding="utf8"):
        """
        Dumps the ascii art in the file.
        Args:
            filename (str): File to dump the ascii art.
            encoding (str): Optional. Default "utf-8".
        """
        with open(filename, mode='w', encoding=encoding) as text_file:
            text_file.write(self.single_string())

    def lines(self, line_length=None):
        """
        Generates a list with lines. These lines form the text drawing.
        Args:
            line_length (int): Optional. Breaks the circuit drawing to this length. This
                               useful when the drawing does not fit in the console. If
                               None (default), it will try to guess the console width using
                               shutil.get_terminal_size(). If you don't want pagination
                               at all, set line_length=-1.

        Returns:
            list: A list of lines with the text drawing.
        """
        if line_length is None:
            line_length = self.line_length
        if line_length is None:
            if ('ipykernel' in sys.modules) and ('spyder' not in sys.modules):
                line_length = 80
            else:
                line_length, _ = get_terminal_size()

        noqubits = len(self.qregs)

        layers = self.build_layers()

        if not line_length:
            line_length = self.line_length

        layer_groups = [[]]
        rest_of_the_line = line_length
        for layerno, layer in enumerate(layers):
            # Replace the Nones with EmptyWire
            layers[layerno] = EmptyWire.fillup_layer(layer, noqubits)

            TextDrawing.normalize_width(layer)

            if line_length == -1:
                # Do not use pagination (aka line breaking. aka ignore line_length).
                layer_groups[-1].append(layer)
                continue

            # chop the layer to the line_length (pager)
            layer_length = layers[layerno][0].length

            if layer_length < rest_of_the_line:
                layer_groups[-1].append(layer)
                rest_of_the_line -= layer_length
            else:
                layer_groups[-1].append(BreakWire.fillup_layer(len(layer), '»'))

                # New group
                layer_groups.append([BreakWire.fillup_layer(len(layer), '«')])
                rest_of_the_line = line_length - layer_groups[-1][-1][0].length

                layer_groups[-1].append(
                    InputWire.fillup_layer(self.wire_names(with_initial_value=False)))
                rest_of_the_line -= layer_groups[-1][-1][0].length

                layer_groups[-1].append(layer)
                rest_of_the_line -= layer_groups[-1][-1][0].length

        lines = []
        for layer_group in layer_groups:
            wires = [i for i in zip(*layer_group)]
            lines += TextDrawing.draw_wires(wires, self.vertically_compressed)

        return lines

    def wire_names(self, with_initial_value=True):
        """
        Returns a list of names for each wire.
        Args:
            with_initial_value (bool): Optional (Default: True). If true, adds the initial value to
                                       the name.

        Returns:
            List: The list of wire names.
        """
        qubit_labels = self._get_qubit_labels()
        clbit_labels = self._get_clbit_labels()

        if with_initial_value:
            qubit_labels = ['%s: |0>' % qubit for qubit in qubit_labels]
            clbit_labels = ['%s: 0 ' % clbit for clbit in clbit_labels]
        else:
            qubit_labels = ['%s: ' % qubit for qubit in qubit_labels]
            clbit_labels = ['%s: ' % clbit for clbit in clbit_labels]

        return qubit_labels + clbit_labels

    @staticmethod
    def draw_wires(wires, vertically_compressed=True):
        """
        Given a list of wires, creates a list of lines with the text drawing.
        Args:
            wires (list): A list of wires with instructions.
            vertically_compressed (bool): Default is `True`. It merges the lines
                                     so the drawing will take less vertical room.
        Returns:
            list: A list of lines with the text drawing.
        """
        lines = []
        bot_line = None
        for wire in wires:
            # TOP
            top_line = ''
            for instruction in wire:
                top_line += instruction.top

            if bot_line is None:
                lines.append(top_line)
            else:
                if vertically_compressed:
                    lines.append(TextDrawing.merge_lines(lines.pop(), top_line))
                else:
                    lines.append(TextDrawing.merge_lines(lines[-1], top_line, icod="bot"))

            # MID
            mid_line = ''
            for instruction in wire:
                mid_line += instruction.mid
            lines.append(TextDrawing.merge_lines(lines[-1], mid_line, icod="bot"))

            # BOT
            bot_line = ''
            for instruction in wire:
                bot_line += instruction.bot
            lines.append(TextDrawing.merge_lines(lines[-1], bot_line, icod="bot"))

        return lines

    @staticmethod
    def label_for_conditional(instruction):
        """ Creates the label for a conditional instruction."""
        return "%s %s" % ('=', instruction.condition[1])

    @staticmethod
    def params_for_label(instruction):
        """Get the params and format them to add them to a label. None if there
         are no params of if the params are numpy.ndarrays."""

        if not hasattr(instruction.op, 'params'):
            return None
        if all([isinstance(param, ndarray) for param in instruction.op.params]):
            return None

        ret = []
        for param in instruction.op.params:
            if isinstance(param, (sympy.Number, float)):
                ret.append('%.5g' % param)
            else:
                ret.append('%s' % param)
        return ret

    @staticmethod
    def label_for_box(instruction):
        """ Creates the label for a box."""
        label = instruction.name.capitalize()
        params = TextDrawing.params_for_label(instruction)
        if params:
            label += "(%s)" % ','.join(params)
        return label

    @staticmethod
    def merge_lines(top, bot, icod="top"):
        """
        Merges two lines (top and bot) in the way that the overlapping make senses.
        Args:
            top (str): the top line
            bot (str): the bottom line
            icod (top or bot): in case of doubt, which line should have priority? Default: "top".
        Returns:
            str: The merge of both lines.
        """
        ret = ""
        for topc, botc in zip(top, bot):
            if topc == botc:
                ret += topc
            elif topc in '┼╪' and botc == " ":
                ret += "│"
            elif topc == " ":
                ret += botc
            elif topc in '┬╥' and botc in " ║│" and icod == "top":
                ret += topc
            elif topc in '┬' and botc == " " and icod == "bot":
                ret += '│'
            elif topc in '╥' and botc == " " and icod == "bot":
                ret += '║'
            elif topc in '┬│' and botc == "═":
                ret += '╪'
            elif topc in '┬│' and botc == "─":
                ret += '┼'
            elif topc in '└┘║│░' and botc == " " and icod == "top":
                ret += topc
            elif topc in '─═' and botc == " " and icod == "top":
                ret += topc
            elif topc in '─═' and botc == " " and icod == "bot":
                ret += botc
            elif topc in "║╥" and botc in "═":
                ret += "╬"
            elif topc in "║╥" and botc in "─":
                ret += "╫"
            elif topc in '╫╬' and botc in " ":
                ret += "║"
            elif topc == '└' and botc == "┌":
                ret += "├"
            elif topc == '┘' and botc == "┐":
                ret += "┤"
            elif botc in "┐┌" and icod == 'top':
                ret += "┬"
            elif topc in "┘└" and botc in "─" and icod == 'top':
                ret += "┴"
            else:
                ret += botc
        return ret

    @staticmethod
    def normalize_width(layer):
        """
        When the elements of the layer have different widths, sets the width to the max elements.
        Args:
            layer (list): A list of elements.
        """
        instructions = [instruction for instruction in filter(lambda x: x is not None, layer)]
        longest = max([instruction.length for instruction in instructions])
        for instruction in instructions:
            instruction.layer_width = longest

    def _instruction_to_gate(self, instruction, layer):
        """ Convert an instruction into its corresponding Gate object, and establish
        any connections it introduces between qubits"""

        current_cons = []
        connection_label = None

        # add in a gate that operates over multiple qubits
        def add_connected_gate(instruction, gates, layer, current_cons):
            for i, gate in enumerate(gates):
                layer.set_qubit(instruction.qargs[i], gate)
                actual_index = self.qregs.index(instruction.qargs[i])
                current_cons.append((actual_index, gate))

        if instruction.name == 'measure':
            gate = MeasureFrom()
            layer.set_qubit(instruction.qargs[0], gate)
            layer.set_clbit(instruction.cargs[0], MeasureTo())

        elif instruction.name in ['barrier', 'snapshot', 'save', 'load',
                                  'noise']:
            # barrier
            if not self.plotbarriers:
                return layer, current_cons, connection_label

            for qubit in instruction.qargs:
                layer.set_qubit(qubit, Barrier())

        elif instruction.name == 'swap':
            # swap
            gates = [Ex() for _ in range(len(instruction.qargs))]
            add_connected_gate(instruction, gates, layer, current_cons)

        elif instruction.name == 'cswap':
            # cswap
            gates = [Bullet(), Ex(), Ex()]
            add_connected_gate(instruction, gates, layer, current_cons)

        elif instruction.name == 'reset':
            layer.set_qubit(instruction.qargs[0], Reset())

        elif instruction.condition is not None:
            # conditional
            cllabel = TextDrawing.label_for_conditional(instruction)
            qulabel = TextDrawing.label_for_box(instruction)

            layer.set_cl_multibox(instruction.condition[0], cllabel, top_connect='┴')
            layer.set_qubit(instruction.qargs[0], BoxOnQuWire(qulabel, bot_connect='┬'))

        elif instruction.name in ['cx', 'CX', 'ccx']:
            # cx/ccx
            gates = [Bullet() for _ in range(len(instruction.qargs) - 1)]
            gates.append(BoxOnQuWire('X'))
            add_connected_gate(instruction, gates, layer, current_cons)

        elif instruction.name == 'cy':
            # cy
            gates = [Bullet(), BoxOnQuWire('Y')]
            add_connected_gate(instruction, gates, layer, current_cons)

        elif instruction.name == 'cz':
            # cz
            gates = [Bullet(), Bullet()]
            add_connected_gate(instruction, gates, layer, current_cons)

        elif instruction.name == 'ch':
            # ch
            gates = [Bullet(), BoxOnQuWire('H')]
            add_connected_gate(instruction, gates, layer, current_cons)

        elif instruction.name == 'cu1':
            # cu1
            connection_label = TextDrawing.params_for_label(instruction)[0]
            gates = [Bullet(), Bullet()]
            add_connected_gate(instruction, gates, layer, current_cons)

        elif instruction.name == 'rzz':
            # rzz
            connection_label = "zz(%s)" % TextDrawing.params_for_label(instruction)[0]
            gates = [Bullet(), Bullet()]
            add_connected_gate(instruction, gates, layer, current_cons)

        elif instruction.name == 'cu3':
            # cu3
            params = TextDrawing.params_for_label(instruction)
            gates = [Bullet(), BoxOnQuWire("U3(%s)" % ','.join(params))]
            add_connected_gate(instruction, gates, layer, current_cons)

        elif instruction.name == 'crz':
            # crz
            label = "Rz(%s)" % TextDrawing.params_for_label(instruction)[0]

            gates = [Bullet(), BoxOnQuWire(label)]
            add_connected_gate(instruction, gates, layer, current_cons)

        elif len(instruction.qargs) == 1 and not instruction.cargs:
            # unitary gate
            layer.set_qubit(instruction.qargs[0],
                            BoxOnQuWire(TextDrawing.label_for_box(instruction)))

        elif len(instruction.qargs) >= 2 and not instruction.cargs:
            # multiple qubit gate
            label = instruction.name
            params = TextDrawing.params_for_label(instruction)
            if params:
                label += "(%s)" % ','.join(params)
            layer.set_qu_multibox(instruction.qargs, label)

        else:
            raise VisualizationError(
                "Text visualizer does not know how to handle this instruction", instruction)

        # sort into the order they were declared in
        # this ensures that connected boxes have lines in the right direction
        current_cons.sort(key=lambda tup: tup[0])
        current_cons = [g for q, g in current_cons]

        return layer, current_cons, connection_label

    def build_layers(self):
        """
        Constructs layers.
        Returns:
            list: List of DrawElements.
        Raises:
            VisualizationError: When the drawing is, for some reason, impossible to be drawn.
        """
        wire_names = self.wire_names(with_initial_value=True)
        if not wire_names:
            return []

        layers = [InputWire.fillup_layer(wire_names)]

        for instruction_layer in self.instructions:
            layer = Layer(self.qregs, self.cregs)

            for instruction in instruction_layer:
                layer, current_connections, connection_label = \
                    self._instruction_to_gate(instruction, layer)

                layer.connections.append((connection_label, current_connections))
                layer.connect_with("│")
            layers.append(layer.full_layer)

        return layers

class Layer:
    """ A layer is the "column" of the circuit. """

    def __init__(self, qregs, cregs):
        self.qregs = qregs
        self.cregs = cregs
        self.qubit_layer = [None] * len(qregs)
        self.connections = []
        self.clbit_layer = [None] * len(cregs)

    @property
    def full_layer(self):
        """
        Returns the composition of qubits and classic wires.
        Returns:
            String: self.qubit_layer + self.clbit_layer
        """
        return self.qubit_layer + self.clbit_layer

    def set_qubit(self, qubit, element):
        """
        Sets the qubit to the element
        Args:
            qubit (qbit): Element of self.qregs.
            element (DrawElement): Element to set in the qubit
        """
        self.qubit_layer[self.qregs.index(qubit)] = element

    def set_clbit(self, clbit, element):
        """
        Sets the clbit to the element
        Args:
            clbit (cbit): Element of self.cregs.
            element (DrawElement): Element to set in the clbit
        """
        self.clbit_layer[self.cregs.index(clbit)] = element

    def _set_multibox(self, wire_type, bits, label, top_connect=None):
        # pylint: disable=invalid-name
        bits = list(bits)
        if wire_type == "cl":
            bit_index = sorted([i for i, x in enumerate(self.cregs) if x in bits])
            bits.sort(key=self.cregs.index)
            qargs = [''] * len(bits)
            set_bit = self.set_clbit
            BoxOnWire = BoxOnClWire
            BoxOnWireTop = BoxOnClWireTop
            BoxOnWireMid = BoxOnClWireMid
            BoxOnWireBot = BoxOnClWireBot
        elif wire_type == "qu":
            bit_index = sorted([i for i, x in enumerate(self.qregs) if x in bits])
            qargs = [str(bits.index(qbit)) for qbit in self.qregs if qbit in bits]
            bits.sort(key=self.qregs.index)
            set_bit = self.set_qubit
            BoxOnWire = BoxOnQuWire
            BoxOnWireTop = BoxOnQuWireTop
            BoxOnWireMid = BoxOnQuWireMid
            BoxOnWireBot = BoxOnQuWireBot
        else:
            raise VisualizationError("_set_multibox only supports 'cl' and 'qu' as wire types.")

        if len(bit_index) == 1:
            set_bit(bits[0], BoxOnWire(label, top_connect=top_connect))
        else:
            box_height = max(bit_index) - min(bit_index) + 1
            set_bit(bits.pop(0),
                    BoxOnWireTop(label, top_connect=top_connect, wire_label=qargs.pop(0)))
            for order, bit_i in enumerate(range(min(bit_index) + 1, max(bit_index))):
                if bit_i in bit_index:
                    named_bit = bits.pop(0)
                    wire_label = qargs.pop(0)
                else:
                    named_bit = (self.qregs + self.cregs)[bit_i]
                    wire_label = ' ' * len(qargs[0])
                set_bit(named_bit, BoxOnWireMid(label, box_height, order, wire_label=wire_label))
            set_bit(bits.pop(0), BoxOnWireBot(label, box_height, wire_label=qargs.pop(0)))

    def set_cl_multibox(self, creg, label, top_connect='┴'):
        """
        Sets the multi clbit box.
        Args:
            creg (string): The affected classical register.
            label (string): The label for the multi clbit box.
            top_connect (char): The char to connect the box on the top.
        """
        clbit = [bit for bit in self.cregs if bit[0] == creg]
        self._set_multibox("cl", clbit, label, top_connect=top_connect)

    def set_qu_multibox(self, bits, label):
        """
        Sets the multi qubit box.
        Args:
            bits (list[int]): A list of affected bits.
            label (string): The label for the multi qubit box.
        """
        self._set_multibox("qu", bits, label)

    def connect_with(self, wire_char):
        """
        Connects the elements in the layer using wire_char.
        Args:
            wire_char (char): For example '║' or '│'.
        """

        if len([qbit for qbit in self.qubit_layer if qbit is not None]) == 1:
            # Nothing to connect
            return

        for label, affected_bits in self.connections:

            if not affected_bits:
                continue

            affected_bits[0].connect(wire_char, ['bot'])
            for affected_bit in affected_bits[1:-1]:
                affected_bit.connect(wire_char, ['bot', 'top'])

            affected_bits[-1].connect(wire_char, ['top'], label)

            if label:
                for affected_bit in affected_bits:
                    affected_bit.right_fill = len(label) + len(affected_bit.mid)


# -*- 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.

"""Node for an OPENQASM id."""

from .node import Node
from .nodeexception import NodeException

class Id(Node):
    """Node for an OPENQASM id.

    The node has no children but has fields name, line, and file.
    There is a flag is_bit that is set when XXXXX to help with scoping.
    """

    def __init__(self, id, line, file):
        """Create the id node."""
        # pylint: disable=redefined-builtin
        super().__init__("id", None, None)
        self.name = id
        self.line = line
        self.file = file
        # To help with scoping rules, so we know the id is a bit,
        # this flag is set to True when the id appears in a gate declaration
        self.is_bit = False

    def to_string(self, indent):
        """Print the node with indent."""
        ind = indent * ' '
        print(ind, 'id', self.name)

    def qasm(self, prec=15):
        """Return the corresponding OPENQASM string."""
        # pylint: disable=unused-argument
        return self.name

    def latex(self, prec=15, nested_scope=None):
        """Return the correspond math mode latex string."""
        if not nested_scope:
            return "\textrm{" + self.name + "}"
        else:
            if self.name not in nested_scope[-1]:
                raise NodeException("Expected local parameter name: ",
                                    "name=%s, " % self.name,
                                    "line=%s, " % self.line,
                                    "file=%s" % self.file)
            else:
                return nested_scope[-1][self.name].latex(prec,
                                                         nested_scope[0:-1])

    def sym(self, nested_scope=None):
        """Return the correspond symbolic number."""
        if not nested_scope or self.name not in nested_scope[-1]:
            raise NodeException("Expected local parameter name: ",
                                "name=%s, line=%s, file=%s" % (
                                    self.name, self.line, self.file))
        else:
            return nested_scope[-1][self.name].sym(nested_scope[0:-1])

    def real(self, nested_scope=None):
        """Return the correspond floating point number."""
        if not nested_scope or self.name not in nested_scope[-1]:
            raise NodeException("Expected local parameter name: ",
                                "name=%s, line=%s, file=%s" % (
                                    self.name, self.line, self.file))
        else:
            return nested_scope[-1][self.name].real(nested_scope[0:-1])