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

"""
Message broker for the Publisher / Subscriber mechanism
"""

from qiskit.exceptions import QiskitError

class _Broker:
    """The event/message broker. It's a singleton.

    In order to keep consistency across all the components, it would be great to
    have a specific format for new events, documenting their usage.
    It's the responsibility of the component emitting an event to document it's usage in
    the component docstring.

    Event format:
        "terra.<component>.<method>.<action>"

    Examples:
        "terra.transpiler.compile.start"
        "terra.job.status.changed"
        "terra.backend.run.start"
        "terra.job.result.received"
    """

    _instance = None
    _subscribers = {}

    def __new__(cls):
        if _Broker._instance is None:
            _Broker._instance = object.__new__(cls)
        return _Broker._instance

    class _Subscription:
        def __init__(self, event, callback):
            self.event = event
            self.callback = callback

        def __eq__(self, other):
            """Overrides the default implementation"""
            if isinstance(other, self.__class__):
                return self.event == other.event and \
                       self.callback.__name__ == other.callback.__name__
            return False

    def subscribe(self, event, callback):
        """Subscribes to an event, so when it's emitted all the callbacks subscribed,
        will be executed. We are not allowing double registration.

        Args
            event (string): The event to subscribed in the form of:
                            "terra.<component>.<method>.<action>"
            callback (callable): The callback that will be executed when an event is
                                  emitted.
        """
        if not callable(callback):
            raise QiskitError("Callback is not a callable!")

        if event not in self._subscribers:
            self._subscribers[event] = []

        new_subscription = self._Subscription(event, callback)
        if new_subscription in self._subscribers[event]:
            # We are not allowing double subscription
            return False

        self._subscribers[event].append(new_subscription)
        return True

    def dispatch(self, event, *args, **kwargs):
        """Emits an event if there are any subscribers.

        Args
            event (String): The event to be emitted
            args: Arguments linked with the event
            kwargs: Named arguments linked with the event
        """
        # No event, no subscribers.
        if event not in self._subscribers:
            return

        for subscriber in self._subscribers[event]:
            subscriber.callback(*args, **kwargs)

    def unsubscribe(self, event, callback):
        """ Unsubscribe the specific callback to the event.

        Args
            event (String): The event to unsubscribe
            callback (callable): The callback that won't be executed anymore

        Returns
            True: if we have successfully unsubscribed to the event
            False: if there's no callback previously registered
        """

        try:
            self._subscribers[event].remove(self._Subscription(event, callback))
        except KeyError:
            return False

        return True

    def clear(self):
        """ Unsubscribe everything, leaving the Broker without subscribers/events.
        """
        self._subscribers.clear()

class Publisher:
    """ Represents a Publisher, every component (class) can become a Publisher and
    send events by inheriting this class. Functions can call this class like:
    Publisher().publish("event", args, ... )
    """
    def __init__(self):
        self._broker = _Broker()

    def publish(self, event, *args, **kwargs):
        """ Triggers an event, and associates some data to it, so if there are any
        subscribers, their callback will be called synchronously. """
        return self._broker.dispatch(event, *args, **kwargs)

class Subscriber:
    """ Represents a Subscriber, every component (class) can become a Subscriber and
    subscribe to events, that will call callback functions when they are emitted.
    """
    def __init__(self):
        self._broker = _Broker()

    def subscribe(self, event, callback):
        """ Subscribes to an event, associating a callback function to that event, so
        when the event occurs, the callback will be called.
        This is a blocking call, so try to keep callbacks as lighweight as possible. """
        return self._broker.subscribe(event, callback)

    def unsubscribe(self, event, callback):
        """ Unsubscribe a pair event-callback, so the callback will not be called anymore
        when the event occurs."""
        return self._broker.unsubscribe(event, callback)

    def clear(self):
        """ Unsubscribe everything"""
        self._broker.clear()


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

"""
Initialize qubit registers to desired arbitrary state.
"""

import math
import numpy as np
import scipy

from qiskit.exceptions import QiskitError
from qiskit.circuit import QuantumCircuit
from qiskit.circuit import QuantumRegister
from qiskit.circuit import Instruction
from qiskit.circuit.decorators import _convert_to_bits
from qiskit.extensions.standard.cx import CnotGate
from qiskit.extensions.standard.ry import RYGate
from qiskit.extensions.standard.rz import RZGate
from qiskit.circuit.reset import Reset

_EPS = 1e-10  # global variable used to chop very small numbers to zero

class Initialize(Instruction):
    """Complex amplitude initialization.

    Class that implements the (complex amplitude) initialization of some
    flexible collection of qubit registers (assuming the qubits are in the
    zero state).
    """

    def __init__(self, params):
        """Create new initialize composite.

        params (list): vector of complex amplitudes to initialize to
        """
        num_qubits = math.log2(len(params))

        # Check if param is a power of 2
        if num_qubits == 0 or not num_qubits.is_integer():
            raise QiskitError("Desired statevector length not a positive power of 2.")

        # Check if probabilities (amplitudes squared) sum to 1
        if not math.isclose(sum(np.absolute(params) ** 2), 1.0,
                            abs_tol=_EPS):
            raise QiskitError("Sum of amplitudes-squared does not equal one.")

        num_qubits = int(num_qubits)

        super().__init__("initialize", num_qubits, 0, params)

    def _define(self):
        """Calculate a subcircuit that implements this initialization

        Implements a recursive initialization algorithm, including optimizations,
        from "Synthesis of Quantum Logic Circuits" Shende, Bullock, Markov
        https://arxiv.org/abs/quant-ph/0406176v5

        Additionally implements some extra optimizations: remove zero rotations and
        double cnots.
        """
        # call to generate the circuit that takes the desired vector to zero
        disentangling_circuit = self.gates_to_uncompute()

        # invert the circuit to create the desired vector from zero (assuming
        # the qubits are in the zero state)
        initialize_instr = disentangling_circuit.to_instruction().inverse()

        q = QuantumRegister(self.num_qubits, 'q')
        initialize_circuit = QuantumCircuit(q, name='init_def')
        for qubit in q:
            initialize_circuit.append(Reset(), [qubit])
        initialize_circuit.append(initialize_instr, q[:])

        self.definition = initialize_circuit.data

    def gates_to_uncompute(self):
        """
        Call to create a circuit with gates that take the
        desired vector to zero.

        Returns:
            QuantumCircuit: circuit to take self.params vector to |00..0>
        """
        q = QuantumRegister(self.num_qubits)
        circuit = QuantumCircuit(q, name='disentangler')

        # kick start the peeling loop, and disentangle one-by-one from LSB to MSB
        remaining_param = self.params

        for i in range(self.num_qubits):
            # work out which rotations must be done to disentangle the LSB
            # qubit (we peel away one qubit at a time)
            (remaining_param,
             thetas,
             phis) = Initialize._rotations_to_disentangle(remaining_param)

            # perform the required rotations to decouple the LSB qubit (so that
            # it can be "factored" out, leaving a shorter amplitude vector to peel away)
            rz_mult = self._multiplex(RZGate, phis)
            ry_mult = self._multiplex(RYGate, thetas)
            circuit.append(rz_mult.to_instruction(), q[i:self.num_qubits])
            circuit.append(ry_mult.to_instruction(), q[i:self.num_qubits])
        return circuit

    @staticmethod
    def _rotations_to_disentangle(local_param):
        """
        Static internal method to work out Ry and Rz rotation angles used
        to disentangle the LSB qubit.
        These rotations make up the block diagonal matrix U (i.e. multiplexor)
        that disentangles the LSB.

        [[Ry(theta_1).Rz(phi_1)  0   .   .   0],
         [0         Ry(theta_2).Rz(phi_2) .  0],
                                    .
                                        .
          0         0           Ry(theta_2^n).Rz(phi_2^n)]]
        """
        remaining_vector = []
        thetas = []
        phis = []

        param_len = len(local_param)

        for i in range(param_len // 2):
            # Ry and Rz rotations to move bloch vector from 0 to "imaginary"
            # qubit
            # (imagine a qubit state signified by the amplitudes at index 2*i
            # and 2*(i+1), corresponding to the select qubits of the
            # multiplexor being in state |i>)
            (remains,
             add_theta,
             add_phi) = Initialize._bloch_angles(local_param[2 * i: 2 * (i + 1)])

            remaining_vector.append(remains)

            # rotations for all imaginary qubits of the full vector
            # to move from where it is to zero, hence the negative sign
            thetas.append(-add_theta)
            phis.append(-add_phi)

        return remaining_vector, thetas, phis

    @staticmethod
    def _bloch_angles(pair_of_complex):
        """
        Static internal method to work out rotation to create the passed in
        qubit from the zero vector.
        """
        [a_complex, b_complex] = pair_of_complex
        # Force a and b to be complex, as otherwise numpy.angle might fail.
        a_complex = complex(a_complex)
        b_complex = complex(b_complex)
        mag_a = np.absolute(a_complex)
        final_r = float(np.sqrt(mag_a ** 2 + np.absolute(b_complex) ** 2))
        if final_r < _EPS:
            theta = 0
            phi = 0
            final_r = 0
            final_t = 0
        else:
            theta = float(2 * np.arccos(mag_a / final_r))
            a_arg = np.angle(a_complex)
            b_arg = np.angle(b_complex)
            final_t = a_arg + b_arg
            phi = b_arg - a_arg

        return final_r * np.exp(1.J * final_t / 2), theta, phi

    def _multiplex(self, target_gate, list_of_angles):
        """
        Return a recursive implementation of a multiplexor circuit,
        where each instruction itself has a decomposition based on
        smaller multiplexors.

        The LSB is the multiplexor "data" and the other bits are multiplexor "select".

        Args:
            target_gate (Gate): Ry or Rz gate to apply to target qubit, multiplexed
                over all other "select" qubits
            list_of_angles (list[float]): list of rotation angles to apply Ry and Rz

        Returns:
            DAGCircuit: the circuit implementing the multiplexor's action
        """
        list_len = len(list_of_angles)
        local_num_qubits = int(math.log2(list_len)) + 1

        q = QuantumRegister(local_num_qubits)
        circuit = QuantumCircuit(q, name="multiplex" + local_num_qubits.__str__())

        lsb = q[0]
        msb = q[local_num_qubits - 1]

        # case of no multiplexing: base case for recursion
        if local_num_qubits == 1:
            circuit.append(target_gate(list_of_angles[0]), [q[0]])
            return circuit

        # calc angle weights, assuming recursion (that is the lower-level
        # requested angles have been correctly implemented by recursion
        angle_weight = scipy.kron([[0.5, 0.5], [0.5, -0.5]],
                                  np.identity(2 ** (local_num_qubits - 2)))

        # calc the combo angles
        list_of_angles = angle_weight.dot(np.array(list_of_angles)).tolist()

        # recursive step on half the angles fulfilling the above assumption
        multiplex_1 = self._multiplex(target_gate, list_of_angles[0:(list_len // 2)])
        circuit.append(multiplex_1.to_instruction(), q[0:-1])

        # attach CNOT as follows, thereby flipping the LSB qubit
        circuit.append(CnotGate(), [msb, lsb])

        # implement extra efficiency from the paper of cancelling adjacent
        # CNOTs (by leaving out last CNOT and reversing (NOT inverting) the
        # second lower-level multiplex)
        multiplex_2 = self._multiplex(target_gate, list_of_angles[(list_len // 2):])
        if list_len > 1:
            circuit.append(multiplex_2.to_instruction().mirror(), q[0:-1])
        else:
            circuit.append(multiplex_2.to_instruction(), q[0:-1])

        # attach a final CNOT
        circuit.append(CnotGate(), [msb, lsb])

        return circuit

def initialize(self, params, qubits):
    """Apply initialize to circuit."""
    if isinstance(qubits, QuantumRegister):
        qubits = qubits[:]
    else:
        qubits = _convert_to_bits([qubits], [qbit for qreg in self.qregs for qbit in qreg])[0]
    return self.append(Initialize(params), qubits)

QuantumCircuit.initialize = initialize


# -*- 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 two-ways dict to represent a layout.

Layout is the relation between virtual (qu)bits and physical (qu)bits.
Virtual (qu)bits are tuples, e.g. `(QuantumRegister(3, 'qr'), 2)` or simply `qr[2]`.
Physical (qu)bits are integers.
"""
import warnings

from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.register import Register
from qiskit.transpiler.exceptions import LayoutError

class Layout():
    """Two-ways dict to represent a Layout."""

    def __init__(self, input_dict=None):
        """construct a Layout from a bijective dictionary, mapping
        virtual qubits to physical qubits"""
        self._p2v = {}
        self._v2p = {}
        if input_dict is not None:
            if not isinstance(input_dict, dict):
                raise LayoutError("Layout constructor takes a dict")
            self.from_dict(input_dict)

    def __repr__(self):
        """Representation of a Layout"""
        str_list = []
        for key, val in self._p2v.items():
            str_list.append("{k}: {v},".format(k=key, v=val))
        if str_list:
            str_list[-1] = str_list[-1][:-1]
        return "Layout({\n" + "\n".join(str_list) + "\n})"

    def from_dict(self, input_dict):
        """
        Populates a Layout from a dictionary.
        The dictionary must be a bijective mapping between
        virtual qubits (tuple) and physical qubits (int).

        Args:
            input_dict (dict):
                e.g.:
                {(QuantumRegister(3, 'qr'), 0): 0,
                 (QuantumRegister(3, 'qr'), 1): 1,
                 (QuantumRegister(3, 'qr'), 2): 2}

                Can be written more concisely as follows:

                virtual to physical:
                    {qr[0]: 0,
                     qr[1]: 1,
                     qr[2]: 2}

                physical to virtual:
                    {0: qr[0],
                     1: qr[1],
                     2: qr[2]}
        """
        # TODO (luciano): Remove this full block after 0.8.
        #  its here to support {("qr", 0): ("q", 0),...}
        if len(input_dict) >= 1:
            key = list(input_dict.keys())[0]
            value = input_dict[key]
            if (isinstance(key, tuple) and  # pylint: disable=too-many-boolean-expressions
                    len(key) == 2 and
                    isinstance(key[0], str) and
                    isinstance(key[1], int) and
                    isinstance(value, tuple) and
                    len(value) == 2 and
                    isinstance(key[0], str) and
                    isinstance(key[1], int)):
                warnings.warn("This form of dictionary (i.e. {(\"%s\",%s):(\"%s\",%s), ...} is "
                              "going to be deprecated after 0.8." % (key + value),
                              DeprecationWarning)
                qreg_names = {qubit[0] for qubit in input_dict.keys()}
                qregs = {}
                for qreg_name in qreg_names:
                    qregs[qreg_name] = QuantumRegister(
                        max([qubit[1] for qubit in input_dict.keys() if qubit[0] == qreg_name]) + 1,
                        qreg_name)
                new_input_dict = {}
                for key, value in input_dict.items():
                    new_input_dict[value[1]] = (qregs[key[0]], key[1])
                input_dict = new_input_dict

        for key, value in input_dict.items():
            virtual, physical = Layout.order_based_on_type(key, value)
            self._p2v[physical] = virtual
            if virtual is None:
                continue
            self._v2p[virtual] = physical

    @staticmethod
    def order_based_on_type(value1, value2):
        """decides which one is physical/virtual based on the type. Returns (virtual, physical)"""
        if isinstance(value1, int) and Layout.is_virtual(value2):
            physical = value1
            virtual = value2
        elif isinstance(value2, int) and Layout.is_virtual(value1):
            physical = value2
            virtual = value1
        else:
            raise LayoutError('The map (%s -> %s) has to be a ((Register, integer) -> integer)'
                              ' or the other way around.' % (type(value1), type(value2)))
        return virtual, physical

    @staticmethod
    def is_virtual(value):
        """Checks if value has the format of a virtual qubit """
        return value is None or isinstance(value, tuple) and len(value) == 2 and isinstance(
            value[0], Register) and isinstance(value[1], int)

    def __getitem__(self, item):
        if item in self._p2v:
            return self._p2v[item]
        if item in self._v2p:
            return self._v2p[item]
        raise KeyError('The item %s does not exist in the Layout' % (item,))

    def __setitem__(self, key, value):
        virtual, physical = Layout.order_based_on_type(key, value)
        self._set_type_checked_item(virtual, physical)

    def _set_type_checked_item(self, virtual, physical):
        old = self._v2p.pop(virtual, None)
        self._p2v.pop(old, None)
        old = self._p2v.pop(physical, None)
        self._v2p.pop(old, None)

        self._p2v[physical] = virtual
        if virtual is not None:
            self._v2p[virtual] = physical

    def __delitem__(self, key):
        if isinstance(key, int):
            del self._p2v[key]
            del self._v2p[self._p2v[key]]
        elif isinstance(key, tuple) and \
                len(key) == 2 and \
                isinstance(key[0], Register) and isinstance(key[1], int):
            del self._v2p[key]
            del self._p2v[self._v2p[key]]
        else:
            raise LayoutError('The key to remove should be of the form'
                              ' (Register, integer) or integer) and %s was provided' % (type(key),))

    def __len__(self):
        return len(self._p2v)

    def copy(self):
        """Returns a copy of a Layout instance."""
        layout_copy = type(self)()

        layout_copy._p2v = self._p2v.copy()
        layout_copy._v2p = self._v2p.copy()

        return layout_copy

    def add(self, virtual_bit, physical_bit=None):
        """
        Adds a map element between `bit` and `physical_bit`. If `physical_bit` is not
        defined, `bit` will be mapped to a new physical bit (extending the length of the
        layout by one.)
        Args:
            virtual_bit (tuple): A (qu)bit. For example, (QuantumRegister(3, 'qr'), 2).
            physical_bit (int): A physical bit. For example, 3.
        """
        if physical_bit is None:
            physical_candidate = len(self)
            while physical_candidate in self._p2v:
                physical_candidate += 1
            physical_bit = physical_candidate
        self[virtual_bit] = physical_bit

    def add_register(self, reg):
        """
        Adds at the end physical_qubits that map each bit in reg.
        Args:
            reg (Register): A (qu)bit Register. For example, QuantumRegister(3, 'qr').
        """
        for bit in reg:
            self.add(bit)

    def get_registers(self):
        """
        Returns the registers in the layout [QuantumRegister(2, 'qr0'), QuantumRegister(3, 'qr1')]
        Returns:
            List: A list of Register in the layout
        """
        return {reg for reg, _ in self.get_virtual_bits()}

    def get_virtual_bits(self):
        """
        Returns the dictionary where the keys are virtual (qu)bits and the
        values are physical (qu)bits.
        """
        return self._v2p

    def get_physical_bits(self):
        """
        Returns the dictionary where the keys are physical (qu)bits and the
        values are virtual (qu)bits.
        """
        return self._p2v

    def swap(self, left, right):
        """Swaps the map between left and right.
        Args:
            left (tuple or int): Item to swap with right.
            right (tuple or int): Item to swap with left.
        Raises:
            LayoutError: If left and right have not the same type.
        """
        if type(left) is not type(right):
            raise LayoutError('The method swap only works with elements of the same type.')
        temp = self[left]
        self[left] = self[right]
        self[right] = temp

    def combine_into_edge_map(self, another_layout):
        """Combines self and another_layout into an "edge map".

        For example::

              self       another_layout  resulting edge map
           qr_1 -> 0        0 <- q_2         qr_1 -> q_2
           qr_2 -> 2        2 <- q_1         qr_2 -> q_1
           qr_3 -> 3        3 <- q_0         qr_3 -> q_0

        The edge map is used to compose dags via, for example, compose_back.

        Args:
            another_layout (Layout): The other layout to combine.
        Returns:
            dict: A "edge map".
        Raises:
            LayoutError: another_layout can be bigger than self, but not smaller. Otherwise, raises.
        """
        edge_map = dict()

        for virtual, physical in self.get_virtual_bits().items():
            if physical not in another_layout._p2v:
                raise LayoutError('The wire_map_from_layouts() method does not support when the'
                                  ' other layout (another_layout) is smaller.')
            edge_map[virtual] = another_layout[physical]

        return edge_map

    @staticmethod
    def generate_trivial_layout(*regs):
        """
        Creates a trivial ("one-to-one") Layout with the registers in `regs`.
        Args:
            *regs (Registers): registers to include in the layout.
        Returns:
            Layout: A layout with all the `regs` in the given order.
        """
        layout = Layout()
        for reg in regs:
            layout.add_register(reg)
        return layout

    @staticmethod
    def from_intlist(int_list, *qregs):
        """Converts a list of integers to a Layout
        mapping virtual qubits (index of the list) to
        physical qubits (the list values).

        Args:
            int_list (list): A list of integers.
            *qregs (QuantumRegisters): The quantum registers to apply
                the layout to.
        Returns:
            Layout: The corresponding Layout object.
        Raises:
            LayoutError: Invalid input layout.
        """
        if not all((isinstance(i, int) for i in int_list)):
            raise LayoutError('Expected a list of ints')
        if len(int_list) != len(set(int_list)):
            raise LayoutError('Duplicate values not permitted; Layout is bijective.')
        n_qubits = sum(reg.size for reg in qregs)
        # Check if list is too short to cover all qubits
        if len(int_list) < n_qubits:
            err_msg = 'Integer list length must equal number of qubits in circuit.'
            raise LayoutError(err_msg)
        out = Layout()
        main_idx = 0
        for qreg in qregs:
            for idx in range(qreg.size):
                out[(qreg, idx)] = int_list[main_idx]
                main_idx += 1
        if main_idx != len(int_list):
            for int_item in int_list[main_idx:]:
                out[int_item] = None
        return out

    @staticmethod
    def from_tuplelist(tuple_list):
        """
        Populates a Layout from a list containing virtual
        qubits---(QuantumRegister, int) tuples---, or None.

        Args:
            tuple_list (list):
                e.g.: [qr[0], None, qr[2], qr[3]]
        Returns:
            Layout: the corresponding Layout object
        Raises:
            LayoutError: If the elements are not (Register, integer) or None
        """
        out = Layout()
        for physical, virtual in enumerate(tuple_list):
            if virtual is None:
                continue
            elif Layout.is_virtual(virtual):
                if virtual in out._v2p:
                    raise LayoutError('Duplicate values not permitted; Layout is bijective.')
                out[virtual] = physical
            else:
                raise LayoutError("The list should contain elements of the form"
                                  " (Register, integer) or None")
        return out


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

"""
Toffoli gate. Controlled-Controlled-X.
"""

import numpy

from qiskit.circuit import CompositeGate
from qiskit.circuit import Gate
from qiskit.circuit import QuantumCircuit
from qiskit.circuit import QuantumRegister
from qiskit.circuit.decorators import _op_expand, _to_bits
from qiskit.extensions.standard.h import HGate
from qiskit.extensions.standard.cx import CnotGate
from qiskit.extensions.standard.t import TGate
from qiskit.extensions.standard.t import TdgGate

class ToffoliGate(Gate):
    """Toffoli gate."""

    def __init__(self):
        """Create new Toffoli gate."""
        super().__init__("ccx", 3, [])

    def _define(self):
        """
        gate ccx a,b,c
        {
        h c; cx b,c; tdg c; cx a,c;
        t c; cx b,c; tdg c; cx a,c;
        t b; t c; h c; cx a,b;
        t a; tdg b; cx a,b;}
        """
        definition = []
        q = QuantumRegister(3, "q")
        rule = [
            (HGate(), [q[2]], []),
            (CnotGate(), [q[1], q[2]], []),
            (TdgGate(), [q[2]], []),
            (CnotGate(), [q[0], q[2]], []),
            (TGate(), [q[2]], []),
            (CnotGate(), [q[1], q[2]], []),
            (TdgGate(), [q[2]], []),
            (CnotGate(), [q[0], q[2]], []),
            (TGate(), [q[1]], []),
            (TGate(), [q[2]], []),
            (HGate(), [q[2]], []),
            (CnotGate(), [q[0], q[1]], []),
            (TGate(), [q[0]], []),
            (TdgGate(), [q[1]], []),
            (CnotGate(), [q[0], q[1]], [])
        ]
        for inst in rule:
            definition.append(inst)
        self.definition = definition

    def inverse(self):
        """Invert this gate."""
        return ToffoliGate()  # self-inverse

    def to_matrix(self):
        """Return a Numpy.array for the Toffoli gate."""
        return numpy.array([[1, 0, 0, 0, 0, 0, 0, 0],
                            [0, 1, 0, 0, 0, 0, 0, 0],
                            [0, 0, 1, 0, 0, 0, 0, 0],
                            [0, 0, 0, 0, 0, 0, 0, 1],
                            [0, 0, 0, 0, 1, 0, 0, 0],
                            [0, 0, 0, 0, 0, 1, 0, 0],
                            [0, 0, 0, 0, 0, 0, 1, 0],
                            [0, 0, 0, 1, 0, 0, 0, 0]], dtype=complex)

@_to_bits(3)
@_op_expand(3, broadcastable=[True, True, False])
def ccx(self, ctl1, ctl2, tgt):
    """Apply Toffoli to from ctl1 and ctl2 to tgt."""
    return self.append(ToffoliGate(), [ctl1, ctl2, tgt], [])

QuantumCircuit.ccx = ccx
CompositeGate.ccx = ccx


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

"""
Traverse the DAG and find blocks of gates that act consecutively on
pairs of qubits. Write the blocks to propert_set as a dictionary
of the form:

    {(q0, q1): [[g0, g1, g2], [g5]],
     (q0, q2): [[g3, g4]]
     ..
     .
    }

Based on implementation by Andrew Cross.
"""

from collections import defaultdict

from qiskit.transpiler.basepasses import AnalysisPass

class Collect2qBlocks(AnalysisPass):
    """Pass to collect sequences of uninterrupted gates acting on 2 qubits.
    """
    def run(self, dag):
        """collect blocks of adjacent gates acting on a pair of "cx" qubits.

        The blocks contain "op" nodes in topological sort order
        such that all gates in a block act on the same pair of
        qubits and are adjacent in the circuit. the blocks are built
        by examining predecessors and successors of "cx" gates in
        the circuit. u1, u2, u3, cx, id gates will be included.

        Return a list of tuples of "op" node labels.
        """
        # Initiate the commutation set
        self.property_set['commutation_set'] = defaultdict(list)

        good_names = ["cx", "u1", "u2", "u3", "id"]
        block_list = []
        nodes = list(dag.topological_nodes())
        nodes_seen = dict(zip(nodes, [False] * len(nodes)))
        for nd in dag.topological_op_nodes():
            group = []
            # Explore predecessors and successors of cx gates
            if nd.name == "cx" and nd.condition is None and not nodes_seen[nd]:
                these_qubits = sorted(nd.qargs)
                # Explore predecessors of the "cx" node
                pred = list(dag.predecessors(nd))
                explore = True
                while explore:
                    pred_next = []
                    # If there is one predecessor, add it if it's on the right qubits
                    if len(pred) == 1 and not nodes_seen[pred[0]]:
                        pnd = pred[0]
                        if pnd.name in good_names:
                            if (pnd.name == "cx" and sorted(pnd.qargs) == these_qubits) or \
                                    pnd.name != "cx":
                                group.append(pnd)
                                nodes_seen[pnd] = True
                                pred_next.extend(dag.predecessors(pnd))
                    # If there are two, then we consider cases
                    elif len(pred) == 2:
                        # First, check if there is a relationship
                        if pred[0] in dag.predecessors(pred[1]):
                            sorted_pred = [pred[1]]   # was [pred[1], pred[0]]
                        elif pred[1] in dag.predecessors(pred[0]):
                            sorted_pred = [pred[0]]   # was [pred[0], pred[1]]
                        else:
                            # We need to avoid accidentally adding a cx on these_qubits
                            # since these must have a dependency through the other predecessor
                            # in this case
                            if pred[0].name == "cx" and sorted(pred[0].qargs) == these_qubits:
                                sorted_pred = [pred[1]]
                            elif pred[1].name == "cx" and sorted(pred[1].qargs) == these_qubits:
                                sorted_pred = [pred[0]]
                            else:
                                sorted_pred = pred
                        if len(sorted_pred) == 2 and sorted_pred[0].name == "cx" and \
                           sorted_pred[1].name == "cx":
                            break  # stop immediately if we hit a pair of cx
                        # Examine each predecessor
                        for pnd in sorted_pred:
                            if pnd.name not in good_names:
                                continue
                            # If a predecessor is a single qubit gate, add it
                            if pnd.name != "cx":
                                if not nodes_seen[pnd]:
                                    group.append(pnd)
                                    nodes_seen[pnd] = True
                                    pred_next.extend(dag.predecessors(pnd))
                            # If cx, check qubits
                            else:
                                pred_qubits = sorted(pnd.qargs)
                                if pred_qubits == these_qubits:
                                    # add if on same qubits
                                    if not nodes_seen[pnd]:
                                        group.append(pnd)
                                        nodes_seen[pnd] = True
                                        pred_next.extend(dag.predecessors(pnd))
                                else:
                                    # remove qubit from consideration if not
                                    these_qubits = list(set(these_qubits) -
                                                        set(pred_qubits))
                    # Update predecessors
                    # Stop if there aren't any more
                    pred = list(set(pred_next))
                    if not pred:
                        explore = False
                # Reverse the predecessor list and append the "cx" node
                group.reverse()
                group.append(nd)
                nodes_seen[nd] = True
                # Reset these_qubits
                these_qubits = sorted(nd.qargs)
                # Explore successors of the "cx" node
                succ = list(dag.successors(nd))
                explore = True
                while explore:
                    succ_next = []
                    # If there is one successor, add it if its on the right qubits
                    if len(succ) == 1 and not nodes_seen[succ[0]]:
                        snd = succ[0]
                        if snd.name in good_names:
                            if (snd.name == "cx" and sorted(snd.qargs) == these_qubits) or \
                                    snd.name != "cx":
                                group.append(snd)
                                nodes_seen[snd] = True
                                succ_next.extend(dag.successors(snd))
                    # If there are two, then we consider cases
                    elif len(succ) == 2:
                        # First, check if there is a relationship
                        if succ[0] in dag.successors(succ[1]):
                            sorted_succ = [succ[1]]  # was [succ[1], succ[0]]
                        elif succ[1] in dag.successors(succ[0]):
                            sorted_succ = [succ[0]]  # was [succ[0], succ[1]]
                        else:
                            # We need to avoid accidentally adding a cx on these_qubits
                            # since these must have a dependency through the other successor
                            # in this case
                            if succ[0].name == "cx" and sorted(succ[0].qargs) == these_qubits:
                                sorted_succ = [succ[1]]
                            elif succ[1].name == "cx" and sorted(succ[1].qargs) == these_qubits:
                                sorted_succ = [succ[0]]
                            else:
                                sorted_succ = succ
                        if len(sorted_succ) == 2 and \
                           sorted_succ[0].name == "cx" and \
                           sorted_succ[1].name == "cx":
                            break  # stop immediately if we hit a pair of cx
                        # Examine each successor
                        for snd in sorted_succ:
                            if snd.name not in good_names:
                                continue
                            # If a successor is a single qubit gate, add it
                            if snd.name != "cx":
                                if not nodes_seen[snd]:
                                    group.append(snd)
                                    nodes_seen[snd] = True
                                    succ_next.extend(dag.successors(snd))
                            else:
                                # If cx, check qubits
                                succ_qubits = sorted(snd.qargs)
                                if succ_qubits == these_qubits:
                                    # add if on same qubits
                                    if not nodes_seen[snd]:
                                        group.append(snd)
                                        nodes_seen[snd] = True
                                        succ_next.extend(dag.successors(snd))
                                else:
                                    # remove qubit from consideration if not
                                    these_qubits = list(set(these_qubits) -
                                                        set(succ_qubits))
                    # Update successors
                    # Stop if there aren't any more
                    succ = list(set(succ_next))
                    if not succ:
                        explore = False

                block_list.append(tuple(group))

        self.property_set['block_list'] = block_list

        return dag


# -*- 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
"""
One-pulse single-qubit gate.
"""
import numpy
from qiskit.circuit import CompositeGate
from qiskit.circuit import Gate
from qiskit.circuit import QuantumCircuit
from qiskit.circuit import QuantumRegister
from qiskit.circuit.decorators import _op_expand, _to_bits
from qiskit.qasm import pi
from qiskit.extensions.standard.u3 import U3Gate

class U2Gate(Gate):
    """One-pulse single-qubit gate."""

    def __init__(self, phi, lam, label=None):
        """Create new one-pulse single-qubit gate."""
        super().__init__("u2", 1, [phi, lam], label=label)

    def _define(self):
        definition = []
        q = QuantumRegister(1, "q")
        rule = [(U3Gate(pi / 2, self.params[0], self.params[1]), [q[0]], [])]
        for inst in rule:
            definition.append(inst)
        self.definition = definition

    def inverse(self):
        """Invert this gate.

        u2(phi,lamb)^dagger = u2(-lamb-pi,-phi+pi)
        """
        return U2Gate(-self.params[1] - pi, -self.params[0] + pi)

    def to_matrix(self):
        """Return a Numpy.array for the U3 gate."""
        isqrt2 = 1 / numpy.sqrt(2)
        phi, lam = self.params
        phi, lam = float(phi), float(lam)
        return numpy.array([[isqrt2, -numpy.exp(1j * lam) * isqrt2],
                            [
                                numpy.exp(1j * phi) * isqrt2,
                                numpy.exp(1j * (phi + lam)) * isqrt2
                            ]],
                           dtype=complex)

@_to_bits(1)
@_op_expand(1)
def u2(self, phi, lam, q):
    """Apply u2 to q."""
    return self.append(U2Gate(phi, lam), [q], [])

QuantumCircuit.u2 = u2
CompositeGate.u2 = u2


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

"""
Instruction = Leaf node of schedule.
"""
import logging
from typing import Tuple, List, Iterable

from qiskit.pulse import ops
from qiskit.pulse.channels import Channel
from qiskit.pulse.interfaces import ScheduleComponent
from qiskit.pulse.timeslots import Interval, Timeslot, TimeslotCollection
from qiskit.pulse.exceptions import PulseError

logger = logging.getLogger(__name__)

# pylint: disable=missing-return-doc

class Instruction(ScheduleComponent):
    """An abstract class for leaf nodes of schedule."""

    def __init__(self, command, *channels: List[Channel],
                 timeslots: TimeslotCollection = None, name=None):
        """
        command (Command): Pulse command to schedule
        *channels: List of pulse channels to schedule with command
        timeslots: Optional list of timeslots. If channels are supplied timeslots
            cannot also be given
        name: Name of Instruction
        """
        self._command = command
        self._name = name if name else self._command.name

        if timeslots and channels:
            raise PulseError('Channels and timeslots may not both be supplied.')

        if not timeslots:
            duration = command.duration
            self._timeslots = TimeslotCollection(*(Timeslot(Interval(0, duration), channel)
                                                   for channel in channels))
        else:
            self._timeslots = timeslots

    @property
    def name(self) -> str:
        """Name of this instruction."""
        return self._name

    @property
    def command(self):
        """Acquire command.

        Returns: Command
        """
        return self._command

    @property
    def channels(self) -> Tuple[Channel]:
        """Returns channels that this schedule uses."""
        return self.timeslots.channels

    @property
    def timeslots(self) -> TimeslotCollection:
        """Occupied time slots by this instruction. """
        return self._timeslots

    @property
    def start_time(self) -> int:
        """Relative begin time of this instruction. """
        return self.timeslots.start_time

    @property
    def stop_time(self) -> int:
        """Relative end time of this instruction. """
        return self.timeslots.stop_time

    @property
    def duration(self) -> int:
        """Duration of this instruction. """
        return self.timeslots.duration

    @property
    def children(self) -> Tuple[ScheduleComponent]:
        """Instruction has no child nodes. """
        return ()

    @property
    def instructions(self) -> Tuple[Tuple[int, 'Instruction']]:
        """Iterable for getting instructions from Schedule tree."""
        return tuple(self._instructions())

    def ch_duration(self, *channels: List[Channel]) -> int:
        """Return duration of the supplied channels in this Instruction.

        Args:
            *channels: Supplied channels
        """
        return self.timeslots.ch_duration(*channels)

    def ch_start_time(self, *channels: List[Channel]) -> int:
        """Return minimum start time for supplied channels.

        Args:
            *channels: Supplied channels
        """
        return self.timeslots.ch_start_time(*channels)

    def ch_stop_time(self, *channels: List[Channel]) -> int:
        """Return maximum start time for supplied channels.

        Args:
            *channels: Supplied channels
        """
        return self.timeslots.ch_stop_time(*channels)

    def _instructions(self, time: int = 0) -> Iterable[Tuple[int, 'Instruction']]:
        """Iterable for flattening Schedule tree.

        Args:
            time: Shifted time of this node due to parent

        Yields:
            Tuple[int, ScheduleComponent]: Tuple containing time `ScheduleComponent` starts
                at and the flattened `ScheduleComponent`.
        """
        yield (time, self)

    def flatten(self) -> 'Instruction':
        """Return itself as already single instruction."""
        return self

    def union(self, *schedules: List[ScheduleComponent]) -> 'ScheduleComponent':
        """Return a new schedule which is the union of `self` and `schedule`.

        Args:
            *schedules: Schedules to be take the union with the parent `Schedule`.
        """
        return ops.union(self, *schedules)

    def shift(self: ScheduleComponent, time: int) -> 'ScheduleComponent':
        """Return a new schedule shifted forward by `time`.

        Args:
            time: Time to shift by
        """
        return ops.shift(self, time)

    def insert(self, start_time: int, schedule: ScheduleComponent) -> 'ScheduleComponent':
        """Return a new schedule with `schedule` inserted within `self` at `start_time`.

        Args:
            start_time: time to be inserted
            schedule: schedule to be inserted
        """
        return ops.insert(self, start_time, schedule)

    def append(self, schedule: ScheduleComponent) -> 'ScheduleComponent':
        """Return a new schedule with `schedule` inserted at the maximum time over
        all channels shared between `self` and `schedule`.

        Args:
            schedule: schedule to be appended
        """
        return ops.append(self, schedule)

    def __add__(self, schedule: ScheduleComponent) -> 'ScheduleComponent':
        """Return a new schedule with `schedule` inserted within `self` at `start_time`."""
        return self.append(schedule)

    def __or__(self, schedule: ScheduleComponent) -> 'ScheduleComponent':
        """Return a new schedule which is the union of `self` and `schedule`."""
        return self.union(schedule)

    def __lshift__(self, time: int) -> 'ScheduleComponent':
        """Return a new schedule which is shifted forward by `time`."""
        return self.shift(time)

    def __repr__(self):
        return "%s" % (self._command)


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

""" Fenced objects are wraps for raising TranspilerAccessError when they are modified."""

from .exceptions import TranspilerAccessError

class FencedObject():
    """ Given an instance and a list of attributes to fence, raises a TranspilerAccessError when one
    of these attributes is accessed."""

    def __init__(self, instance, attributes_to_fence):
        self._wrapped = instance
        self._attributes_to_fence = attributes_to_fence

    def __getattribute__(self, name):
        object.__getattribute__(self, '_check_if_fenced')(name)
        return getattr(object.__getattribute__(self, '_wrapped'), name)

    def __getitem__(self, key):
        object.__getattribute__(self, '_check_if_fenced')('__getitem__')
        return object.__getattribute__(self, '_wrapped')[key]

    def __setitem__(self, key, value):
        object.__getattribute__(self, '_check_if_fenced')('__setitem__')
        object.__getattribute__(self, '_wrapped')[key] = value

    def _check_if_fenced(self, name):
        """
        Checks if the attribute name is in the list of attributes to protect. If so, raises
        TranspilerAccessError.

        Args:
            name (string): the attribute name to check

        Raises:
            TranspilerAccessError: when name is the list of attributes to protect.
        """
        if name in object.__getattribute__(self, '_attributes_to_fence'):
            raise TranspilerAccessError("The fenced %s has the property %s protected" %
                                        (type(object.__getattribute__(self, '_wrapped')), name))

class FencedPropertySet(FencedObject):
    """ A property set that cannot be written (via __setitem__) """
    def __init__(self, property_set_instance):
        super().__init__(property_set_instance, ['__setitem__'])

class FencedDAGCircuit(FencedObject):
    """ A dag circuit that cannot be modified (via remove_op_node) """
    # FIXME: add more fenced methods of the dag after dagcircuit rewrite
    def __init__(self, dag_circuit_instance):
        super().__init__(dag_circuit_instance, ['remove_op_node'])


# -*- 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.
"""
Stinespring representation of a Quantum Channel.

The Stinespring representation for a quantum channel E is given by a rectangular matrix A such that

    E(ρ) = Tr_2[A.ρ.A^dagger]

A general operator map G can also be written using the generalized Kraus representation which
is given by two matrices A, B such that

    G(ρ) = Tr_2[A.ρ.B^dagger]

See [1] for further details.

References:
    [1] C.J. Wood, J.D. Biamonte, D.G. Cory, Quant. Inf. Comp. 15, 0579-0811 (2015)
        Open access: arXiv:1111.6950 [quant-ph]
"""

from numbers import Number

import numpy as np

from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.instruction import Instruction
from qiskit.qiskiterror import QiskitError
from qiskit.quantum_info.operators.predicates import is_identity_matrix
from qiskit.quantum_info.operators.channel.quantum_channel import QuantumChannel
from qiskit.quantum_info.operators.channel.kraus import Kraus
from qiskit.quantum_info.operators.channel.choi import Choi
from qiskit.quantum_info.operators.channel.superop import SuperOp
from qiskit.quantum_info.operators.channel.transformations import _to_stinespring

class Stinespring(QuantumChannel):
    """Stinespring representation of a quantum channel"""

    def __init__(self, data, input_dims=None, output_dims=None):
        """Initialize a quantum channel Stinespring operator.

        Args:
            data (QuantumCircuit or
                  Instruction or
                  BaseOperator or
                  matrix): data to initialize superoperator.
            input_dims (tuple): the input subsystem dimensions.
                                [Default: None]
            output_dims (tuple): the output subsystem dimensions.
                                 [Default: None]

        Raises:
            QiskitError: if input data cannot be initialized as a
            a list of Kraus matrices.

        Additional Information
        ----------------------
        If the input or output dimensions are None, they will be
        automatically determined from the input data. This can fail for the
        Stinespring operator if the output dimension cannot be automatically
        determined.
        """
        # If the input is a list or tuple we assume it is a pair of general
        # Stinespring matrices. If it is a numpy array we assume that it is
        # a single stinespring matrix.
        if isinstance(data, (list, tuple, np.ndarray)):
            if not isinstance(data, tuple):
                # Convert single Stinespring set to length 1 tuple
                stine = (np.array(data, dtype=complex), None)
            if isinstance(data, tuple) and len(data) == 2:
                if data[1] is None:
                    stine = (np.array(data[0], dtype=complex), None)
                else:
                    stine = (np.array(data[0], dtype=complex),
                             np.array(data[1], dtype=complex))

            dim_left, dim_right = stine[0].shape
            # If two stinespring matrices check they are same shape
            if stine[1] is not None:
                if stine[1].shape != (dim_left, dim_right):
                    raise QiskitError("Invalid Stinespring input.")
            input_dim = dim_right
            if output_dims:
                output_dim = np.product(output_dims)
            else:
                output_dim = input_dim
            if dim_left % output_dim != 0:
                raise QiskitError("Invalid output_dim")
        else:
            # Otherwise we initialize by conversion from another Qiskit
            # object into the QuantumChannel.
            if isinstance(data, (QuantumCircuit, Instruction)):
                # If the input is a Terra QuantumCircuit or Instruction we
                # convert it to a SuperOp
                data = SuperOp._instruction_to_superop(data)
            else:
                # We use the QuantumChannel init transform to intialize
                # other objects into a QuantumChannel or Operator object.
                data = self._init_transformer(data)
            data = self._init_transformer(data)
            input_dim, output_dim = data.dim
            # Now that the input is an operator we convert it to a
            # Stinespring operator
            stine = _to_stinespring(data.rep, data._data, input_dim,
                                    output_dim)
            if input_dims is None:
                input_dims = data.input_dims()
            if output_dims is None:
                output_dims = data.output_dims()

        # Check and format input and output dimensions
        input_dims = self._automatic_dims(input_dims, input_dim)
        output_dims = self._automatic_dims(output_dims, output_dim)
        # Initialize either single or general Stinespring
        if stine[1] is None or (stine[1] == stine[0]).all():
            # Standard Stinespring map
            super().__init__(
                'Stinespring', (stine[0], None),
                input_dims=input_dims,
                output_dims=output_dims)
        else:
            # General (non-CPTP) Stinespring map
            super().__init__(
                'Stinespring',
                stine,
                input_dims=input_dims,
                output_dims=output_dims)

    @property
    def data(self):
        # Override to deal with data being either tuple or not
        if self._data[1] is None:
            return self._data[0]
        else:
            return self._data

    def is_cptp(self, atol=None, rtol=None):
        """Return True if completely-positive trace-preserving."""
        if atol is None:
            atol = self._atol
        if rtol is None:
            rtol = self._rtol
        if self._data[1] is not None:
            return False
        check = np.dot(np.transpose(np.conj(self._data[0])), self._data[0])
        return is_identity_matrix(check, rtol=self._rtol, atol=self._atol)

    def conjugate(self):
        """Return the conjugate of the QuantumChannel."""
        # pylint: disable=assignment-from-no-return
        stine_l = np.conjugate(self._data[0])
        stine_r = None
        if self._data[1] is not None:
            stine_r = np.conjugate(self._data[1])
        return Stinespring((stine_l, stine_r), self.input_dims(),
                           self.output_dims())

    def transpose(self):
        """Return the transpose of the QuantumChannel."""
        din, dout = self.dim
        dtr = self._data[0].shape[0] // dout
        stine = [None, None]
        for i, mat in enumerate(self._data):
            if mat is not None:
                stine[i] = np.reshape(
                    np.transpose(np.reshape(mat, (dout, dtr, din)), (2, 1, 0)),
                    (din * dtr, dout))
        return Stinespring(
            tuple(stine),
            input_dims=self.output_dims(),
            output_dims=self.input_dims())

    def compose(self, other, qargs=None, front=False):
        """Return the composition channel self∘other.

        Args:
            other (QuantumChannel): a quantum channel subclass.
            qargs (list): a list of subsystem positions to compose other on.
            front (bool): If False compose in standard order other(self(input))
                          otherwise compose in reverse order self(other(input))
                          [default: False]

        Returns:
            Stinespring: The composition channel as a Stinespring object.

        Raises:
            QiskitError: if other cannot be converted to a channel or
            has incompatible dimensions.
        """
        if qargs is not None:
            return Stinespring(
                SuperOp(self).compose(other, qargs=qargs, front=front))

        # Convert other to Kraus
        if not isinstance(other, Kraus):
            other = Kraus(other)
        # Check dimensions match up
        if front and self._input_dim != other._output_dim:
            raise QiskitError(
                'input_dim of self must match output_dim of other')
        if not front and self._output_dim != other._input_dim:
            raise QiskitError(
                'input_dim of other must match output_dim of self')
        # Since we cannot directly compose two channels in Stinespring
        # representation we convert to the Kraus representation
        return Stinespring(Kraus(self).compose(other, front=front))

    def power(self, n):
        """The matrix power of the channel.

        Args:
            n (int): compute the matrix power of the superoperator matrix.

        Returns:
            Stinespring: the matrix power of the SuperOp converted to a
            Stinespring channel.

        Raises:
            QiskitError: if the input and output dimensions of the
            QuantumChannel are not equal, or the power is not an integer.
        """
        if n > 0:
            return super().power(n)
        return Stinespring(SuperOp(self).power(n))

    def tensor(self, other):
        """Return the tensor product channel self ⊗ other.

        Args:
            other (QuantumChannel): a quantum channel subclass.

        Returns:
            Stinespring: the tensor product channel other ⊗ self as a
            Stinespring object.

        Raises:
            QiskitError: if other cannot be converted to a channel.
        """
        return self._tensor_product(other, reverse=False)

    def expand(self, other):
        """Return the tensor product channel other ⊗ self.

        Args:
            other (QuantumChannel): a quantum channel subclass.

        Returns:
            Stinespring: the tensor product channel other ⊗ self as a
            Stinespring object.

        Raises:
            QiskitError: if other cannot be converted to a channel.
        """
        return self._tensor_product(other, reverse=True)

    def add(self, other):
        """Return the QuantumChannel self + other.

        Args:
            other (QuantumChannel): a quantum channel subclass.

        Returns:
            Stinespring: the linear addition self + other as a
            Stinespring object.

        Raises:
            QiskitError: if other cannot be converted to a channel or
            has incompatible dimensions.
        """
        # Since we cannot directly add two channels in the Stinespring
        # representation we convert to the Choi representation
        return Stinespring(Choi(self).add(other))

    def subtract(self, other):
        """Return the QuantumChannel self - other.

        Args:
            other (QuantumChannel): a quantum channel subclass.

        Returns:
            Stinespring: the linear subtraction self - other as
            Stinespring object.

        Raises:
            QiskitError: if other cannot be converted to a channel or
            has incompatible dimensions.
        """
        # Since we cannot directly subtract two channels in the Stinespring
        # representation we convert to the Choi representation
        return Stinespring(Choi(self).subtract(other))

    def multiply(self, other):
        """Return the QuantumChannel self + other.

        Args:
            other (complex): a complex number.

        Returns:
            Stinespring: the scalar multiplication other * self as a
            Stinespring object.

        Raises:
            QiskitError: if other is not a valid scalar.
        """
        if not isinstance(other, Number):
            raise QiskitError("other is not a number")
        # If the number is complex or negative we need to convert to
        # general Stinespring representation so we first convert to
        # the Choi representation
        if isinstance(other, complex) or other < 1:
            # Convert to Choi-matrix
            return Stinespring(Choi(self).multiply(other))
        # If the number is real we can update the Kraus operators
        # directly
        num = np.sqrt(other)
        stine_l, stine_r = self._data
        stine_l = num * self._data[0]
        stine_r = None
        if self._data[1] is not None:
            stine_r = num * self._data[1]
        return Stinespring((stine_l, stine_r), self.input_dims(),
                           self.output_dims())

    def _evolve(self, state, qargs=None):
        """Evolve a quantum state by the QuantumChannel.

        Args:
            state (QuantumState): The input statevector or density matrix.
            qargs (list): a list of QuantumState subsystem positions to apply
                           the operator on.

        Returns:
            QuantumState: the output quantum state.

        Raises:
            QiskitError: if the operator dimension does not match the
            specified QuantumState subsystem dimensions.
        """
        # If subsystem evolution we use the SuperOp representation
        if qargs is not None:
            return SuperOp(self)._evolve(state, qargs)

        # Otherwise we compute full evolution directly
        state = self._format_state(state)
        if state.shape[0] != self._input_dim:
            raise QiskitError(
                "QuantumChannel input dimension is not equal to state dimension."
            )
        if state.ndim == 1 and self._data[1] is None and \
           self._data[0].shape[0] // self._output_dim == 1:
            # If the shape of the stinespring operator is equal to the output_dim
            # evolution of a state vector psi -> stine.psi
            return np.dot(self._data[0], state)
        # Otherwise we always return a density matrix
        state = self._format_state(state, density_matrix=True)
        stine_l, stine_r = self._data
        if stine_r is None:
            stine_r = stine_l
        din, dout = self.dim
        dtr = stine_l.shape[0] // dout
        shape = (dout, dtr, din)
        return np.einsum('iAB,BC,jAC->ij', np.reshape(stine_l, shape), state,
                         np.reshape(np.conjugate(stine_r), shape))

    def _tensor_product(self, other, reverse=False):
        """Return the tensor product channel.

        Args:
            other (QuantumChannel): a quantum channel subclass.
            reverse (bool): If False return self ⊗ other, if True return
                            if True return (other ⊗ self) [Default: False]
        Returns:
            Stinespring: the tensor product channel as a Stinespring object.

        Raises:
            QiskitError: if other cannot be converted to a channel.
        """
        # Convert other to Stinespring
        if not isinstance(other, Stinespring):
            other = Stinespring(other)

        # Tensor stinespring ops
        sa_l, sa_r = self._data
        sb_l, sb_r = other._data

        # Reshuffle tensor dimensions
        din_a, dout_a = self.dim
        din_b, dout_b = other.dim
        dtr_a = sa_l.shape[0] // dout_a
        dtr_b = sb_l.shape[0] // dout_b
        if reverse:
            shape_in = (dout_b, dtr_b, dout_a, dtr_a, din_b * din_a)
            shape_out = (dout_b * dtr_b * dout_a * dtr_a, din_b * din_a)
        else:
            shape_in = (dout_a, dtr_a, dout_b, dtr_b, din_a * din_b)
            shape_out = (dout_a * dtr_a * dout_b * dtr_b, din_a * din_b)

        # Compute left stinepsring op
        if reverse:
            input_dims = self.input_dims() + other.input_dims()
            output_dims = self.output_dims() + other.output_dims()
            sab_l = np.kron(sb_l, sa_l)
        else:
            input_dims = other.input_dims() + self.input_dims()
            output_dims = other.output_dims() + self.output_dims()
            sab_l = np.kron(sa_l, sb_l)
        # Reravel indicies
        sab_l = np.reshape(
            np.transpose(np.reshape(sab_l, shape_in), (0, 2, 1, 3, 4)),
            shape_out)

        # Compute right stinespring op
        if sa_r is None and sb_r is None:
            sab_r = None
        else:
            if sa_r is None:
                sa_r = sa_l
            elif sb_r is None:
                sb_r = sb_l
            if reverse:
                sab_r = np.kron(sb_r, sa_r)
            else:
                sab_r = np.kron(sa_r, sb_r)
            # Reravel indicies
            sab_r = np.reshape(
                np.transpose(np.reshape(sab_r, shape_in), (0, 2, 1, 3, 4)),
                shape_out)
        return Stinespring((sab_l, sab_r), input_dims, output_dims)


# -*- 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 pass implementing a basic mapper.

The basic mapper is a minimum effort to insert swap gates to map the DAG into a coupling map. When
a cx is not in the coupling map possibilities, it inserts one or more swaps in front to make it
compatible.
"""

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.dagcircuit import DAGCircuit
from qiskit.transpiler import Layout
from qiskit.extensions.standard import SwapGate
from .barrier_before_final_measurements import BarrierBeforeFinalMeasurements

class BasicSwap(TransformationPass):
    """
    Maps (with minimum effort) a DAGCircuit onto a `coupling_map` adding swap gates.
    """

    def __init__(self,
                 coupling_map,
                 initial_layout=None):
        """
        Maps a DAGCircuit onto a `coupling_map` using swap gates.
        Args:
            coupling_map (CouplingMap): Directed graph represented a coupling map.
            initial_layout (Layout): initial layout of qubits in mapping
        """
        super().__init__()
        self.coupling_map = coupling_map
        self.initial_layout = initial_layout
        self.requires.append(BarrierBeforeFinalMeasurements())

    def run(self, dag):
        """
        Runs the BasicSwap pass on `dag`.
        Args:
            dag (DAGCircuit): DAG to map.

        Returns:
            DAGCircuit: A mapped DAG.

        Raises:
            TranspilerError: if the coupling map or the layout are not
            compatible with the DAG
        """
        new_dag = DAGCircuit()

        if self.initial_layout is None:
            if self.property_set["layout"]:
                self.initial_layout = self.property_set["layout"]
            else:
                self.initial_layout = Layout.generate_trivial_layout(*dag.qregs.values())

        if len(dag.qubits()) != len(self.initial_layout):
            raise TranspilerError('The layout does not match the amount of qubits in the DAG')

        if len(self.coupling_map.physical_qubits) != len(self.initial_layout):
            raise TranspilerError(
                "Mappers require to have the layout to be the same size as the coupling map")

        current_layout = self.initial_layout.copy()

        for layer in dag.serial_layers():
            subdag = layer['graph']

            for gate in subdag.twoQ_gates():
                physical_q0 = current_layout[gate.qargs[0]]
                physical_q1 = current_layout[gate.qargs[1]]
                if self.coupling_map.distance(physical_q0, physical_q1) != 1:
                    # Insert a new layer with the SWAP(s).
                    swap_layer = DAGCircuit()

                    path = self.coupling_map.shortest_undirected_path(physical_q0, physical_q1)
                    for swap in range(len(path) - 2):
                        connected_wire_1 = path[swap]
                        connected_wire_2 = path[swap + 1]

                        qubit_1 = current_layout[connected_wire_1]
                        qubit_2 = current_layout[connected_wire_2]

                        # create qregs
                        for qreg in current_layout.get_registers():
                            if qreg not in swap_layer.qregs.values():
                                swap_layer.add_qreg(qreg)

                        # create the swap operation
                        swap_layer.apply_operation_back(SwapGate(),
                                                        qargs=[qubit_1, qubit_2],
                                                        cargs=[])

                    # layer insertion
                    edge_map = current_layout.combine_into_edge_map(self.initial_layout)
                    new_dag.compose_back(swap_layer, edge_map)

                    # update current_layout
                    for swap in range(len(path) - 2):
                        current_layout.swap(path[swap], path[swap + 1])

            edge_map = current_layout.combine_into_edge_map(self.initial_layout)
            new_dag.extend_back(subdag, edge_map)

        return new_dag


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

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

"""
A pass implementing the default Qiskit stochastic mapper.
"""

from logging import getLogger
from pprint import pformat
from math import inf
import numpy as np

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.dagcircuit import DAGCircuit
from qiskit.extensions.standard import SwapGate
from qiskit.transpiler import Layout
from .barrier_before_final_measurements import BarrierBeforeFinalMeasurements
# pylint: disable=no-name-in-module, import-error
from .cython.stochastic_swap.utils import nlayout_from_layout
# pylint: disable=no-name-in-module, import-error
from .cython.stochastic_swap.swap_trial import swap_trial
logger = getLogger(__name__)

# Notes:
# 1. Measurements may occur and be followed by swaps that result in repeated
# measurement of the same qubit. Near-term experiments cannot implement
# these circuits, so some care is required when using this mapper
# with experimental backend targets.
# 2. We do not use the fact that the input state is zero to simplify
# the circuit.

class StochasticSwap(TransformationPass):
    """
    Maps a DAGCircuit onto a `coupling_map` adding swap gates.

    Uses a randomized algorithm.
    """

    def __init__(self, coupling_map, initial_layout=None,
                 trials=20, seed=None):
        """
        Map a DAGCircuit onto a `coupling_map` using swap gates.

        If initial_layout is not None, we assume the input circuit
        has been layed out before running this pass, and that
        the layout process yields a DAG, coupling map, and layout
        with the following properties:

        1. All three have the same number of qubits
        2. The layout a bijection from the DAG qubits to the coupling map

        For this mapping pass, it may also be necessary that

        3. The coupling map is a connected graph

        If these are not satisfied, the behavior is undefined.

        Args:
            coupling_map (CouplingMap): Directed graph representing a coupling
                map.
            initial_layout (Layout): initial layout of qubits in mapping
            trials (int): maximum number of iterations to attempt
            seed (int): seed for random number generator
        """
        super().__init__()
        self.coupling_map = coupling_map
        self.initial_layout = initial_layout
        self.input_layout = None
        self.trials = trials
        self.seed = seed
        self.qregs = None
        self.rng = None
        self.requires.append(BarrierBeforeFinalMeasurements())

    def run(self, dag):
        """
        Run the StochasticSwap pass on `dag`.

        Args:
            dag (DAGCircuit): DAG to map.

        Returns:
            DAGCircuit: A mapped DAG.

        Raises:
            TranspilerError: if the coupling map or the layout are not
            compatible with the DAG
        """

        if self.initial_layout is None:
            if self.property_set["layout"]:
                self.initial_layout = self.property_set["layout"]
            else:
                self.initial_layout = Layout.generate_trivial_layout(*dag.qregs.values())

        if len(dag.qubits()) != len(self.initial_layout):
            raise TranspilerError('The layout does not match the amount of qubits in the DAG')

        if len(self.coupling_map.physical_qubits) != len(self.initial_layout):
            raise TranspilerError(
                "Mappers require to have the layout to be the same size as the coupling map")

        self.input_layout = self.initial_layout.copy()

        self.qregs = dag.qregs
        if self.seed is None:
            self.seed = np.random.randint(0, np.iinfo(np.int32).max)
        self.rng = np.random.RandomState(self.seed)
        logger.debug("StochasticSwap RandomState seeded with seed=%s", self.seed)

        new_dag = self._mapper(dag, self.coupling_map, trials=self.trials)
        # self.property_set["layout"] = self.initial_layout
        return new_dag

    def _layer_permutation(self, layer_partition, layout, qubit_subset,
                           coupling, trials):
        """Find a swap circuit that implements a permutation for this layer.

        The goal is to swap qubits such that qubits in the same two-qubit gates
        are adjacent.

        Based on S. Bravyi's algorithm.

        layer_partition (list): The layer_partition is a list of (qu)bit
            lists and each qubit is a tuple (qreg, index).
        layout (Layout): The layout is a Layout object mapping virtual
            qubits in the input circuit to physical qubits in the coupling
            graph. It reflects the current positions of the data.
        qubit_subset (list): The qubit_subset is the set of qubits in
            the coupling graph that we have chosen to map into, as tuples
            (Register, index).
        coupling (CouplingMap): Directed graph representing a coupling map.
            This coupling map should be one that was provided to the
            stochastic mapper.
        trials (int): Number of attempts the randomized algorithm makes.

        Returns:
            Tuple: success_flag, best_circuit, best_depth, best_layout, trivial_flag

        If success_flag is True, then best_circuit contains a DAGCircuit with
        the swap circuit, best_depth contains the depth of the swap circuit,
        and best_layout contains the new positions of the data qubits after the
        swap circuit has been applied. The trivial_flag is set if the layer
        has no multi-qubit gates.

        Raises:
            TranspilerError: if anything went wrong.
     """
        return _layer_permutation(layer_partition, self.initial_layout,
                                  layout, qubit_subset,
                                  coupling, trials,
                                  self.qregs, self.rng)

    def _layer_update(self, i, first_layer, best_layout, best_depth,
                      best_circuit, layer_list):
        """Provide a DAGCircuit for a new mapped layer.

        i (int) = layer number
        first_layer (bool) = True if this is the first layer in the
            circuit with any multi-qubit gates
        best_layout (Layout) = layout returned from _layer_permutation
        best_depth (int) = depth returned from _layer_permutation
        best_circuit (DAGCircuit) = swap circuit returned
            from _layer_permutation
        layer_list (list) = list of DAGCircuit objects for each layer,
            output of DAGCircuit layers() method

        Return a DAGCircuit object to append to the output DAGCircuit
        that the _mapper method is building.
        """
        layout = best_layout
        logger.debug("layer_update: layout = %s", pformat(layout))
        logger.debug("layer_update: self.initial_layout = %s", pformat(self.initial_layout))
        dagcircuit_output = DAGCircuit()
        for register in layout.get_virtual_bits().keys():
            if register[0] not in dagcircuit_output.qregs.values():
                dagcircuit_output.add_qreg(register[0])

        # If this is the first layer with multi-qubit gates,
        # output all layers up to this point and ignore any
        # swap gates. Set the initial layout.
        if first_layer:
            logger.debug("layer_update: first multi-qubit gate layer")
            # Output all layers up to this point
            for j in range(i + 1):
                # Make qubit edge map and extend by classical bits
                edge_map = layout.combine_into_edge_map(self.initial_layout)
                for bit in dagcircuit_output.clbits():
                    edge_map[bit] = bit
                dagcircuit_output.compose_back(layer_list[j]["graph"], edge_map)
        # Otherwise, we output the current layer and the associated swap gates.
        else:
            # Output any swaps
            if best_depth > 0:
                logger.debug("layer_update: there are swaps in this layer, "
                             "depth %d", best_depth)
                dagcircuit_output.extend_back(best_circuit)
            else:
                logger.debug("layer_update: there are no swaps in this layer")
            # Make qubit edge map and extend by classical bits
            edge_map = layout.combine_into_edge_map(self.initial_layout)
            for bit in dagcircuit_output.clbits():
                edge_map[bit] = bit
            # Output this layer
            dagcircuit_output.compose_back(layer_list[i]["graph"], edge_map)

        return dagcircuit_output

    def _mapper(self, circuit_graph, coupling_graph,
                trials=20):
        """Map a DAGCircuit onto a CouplingMap using swap gates.

        Use self.initial_layout for the initial layout.

        Args:
            circuit_graph (DAGCircuit): input DAG circuit
            coupling_graph (CouplingMap): coupling graph to map onto
            trials (int): number of trials.

        Returns:
            DAGCircuit: object containing a circuit equivalent to
                circuit_graph that respects couplings in coupling_graph
            Layout: a layout object mapping qubits of circuit_graph into
                qubits of coupling_graph. The layout may differ from the
                initial_layout if the first layer of gates cannot be
                executed on the initial_layout, since in this case
                it is more efficient to modify the layout instead of swapping
            Dict: a final-layer qubit permutation

        Raises:
            TranspilerError: if there was any error during the mapping
                or with the parameters.
        """
        # Schedule the input circuit by calling layers()
        layerlist = list(circuit_graph.layers())
        logger.debug("schedule:")
        for i, v in enumerate(layerlist):
            logger.debug("    %d: %s", i, v["partition"])

        if self.initial_layout is not None:
            qubit_subset = self.initial_layout.get_virtual_bits().keys()
        else:
            # Supply a default layout for this dag
            self.initial_layout = Layout()
            physical_qubit = 0
            for qreg in circuit_graph.qregs.values():
                for index in range(qreg.size):
                    self.initial_layout[(qreg, index)] = physical_qubit
                    physical_qubit += 1
            qubit_subset = self.initial_layout.get_virtual_bits().keys()
            # Restrict the coupling map to the image of the layout
            coupling_graph = coupling_graph.subgraph(
                self.initial_layout.get_physical_bits().keys())
            if coupling_graph.size() < len(self.initial_layout):
                raise TranspilerError("Coupling map too small for default layout")
            self.input_layout = self.initial_layout.copy()

        # Find swap circuit to preceed to each layer of input circuit
        layout = self.initial_layout.copy()

        # Construct an empty DAGCircuit with the same set of
        # qregs and cregs as the input circuit
        dagcircuit_output = DAGCircuit()
        dagcircuit_output.name = circuit_graph.name
        for qreg in circuit_graph.qregs.values():
            dagcircuit_output.add_qreg(qreg)
        for creg in circuit_graph.cregs.values():
            dagcircuit_output.add_creg(creg)

        # Make a trivial wire mapping between the subcircuits
        # returned by _layer_update and the circuit we build
        identity_wire_map = {}
        for qubit in circuit_graph.qubits():
            identity_wire_map[qubit] = qubit
        for bit in circuit_graph.clbits():
            identity_wire_map[bit] = bit

        first_layer = True  # True until first layer is output
        logger.debug("initial_layout = %s", layout)

        # Iterate over layers
        for i, layer in enumerate(layerlist):

            # Attempt to find a permutation for this layer
            success_flag, best_circuit, best_depth, best_layout, trivial_flag \
                = self._layer_permutation(layer["partition"], layout,
                                          qubit_subset, coupling_graph,
                                          trials)
            logger.debug("mapper: layer %d", i)
            logger.debug("mapper: success_flag=%s,best_depth=%s,trivial_flag=%s",
                         success_flag, str(best_depth), trivial_flag)

            # If this fails, try one gate at a time in this layer
            if not success_flag:
                logger.debug("mapper: failed, layer %d, "
                             "retrying sequentially", i)
                serial_layerlist = list(layer["graph"].serial_layers())

                # Go through each gate in the layer
                for j, serial_layer in enumerate(serial_layerlist):

                    success_flag, best_circuit, best_depth, best_layout, trivial_flag = \
                        self._layer_permutation(
                            serial_layer["partition"],
                            layout, qubit_subset,
                            coupling_graph,
                            trials)
                    logger.debug("mapper: layer %d, sublayer %d", i, j)
                    logger.debug("mapper: success_flag=%s,best_depth=%s,"
                                 "trivial_flag=%s",
                                 success_flag, str(best_depth), trivial_flag)

                    # Give up if we fail again
                    if not success_flag:
                        raise TranspilerError("swap mapper failed: " +
                                              "layer %d, sublayer %d" % (i, j))

                    # If this layer is only single-qubit gates,
                    # and we have yet to see multi-qubit gates,
                    # continue to the next inner iteration
                    if trivial_flag and first_layer:
                        logger.debug("mapper: skip to next sublayer")
                        continue

                    if first_layer:
                        self.initial_layout = layout

                    # Update the record of qubit positions
                    # for each inner iteration
                    layout = best_layout
                    # Update the DAG
                    dagcircuit_output.extend_back(
                        self._layer_update(j,
                                           first_layer,
                                           best_layout,
                                           best_depth,
                                           best_circuit,
                                           serial_layerlist),
                        identity_wire_map)
                    if first_layer:
                        first_layer = False

            else:
                # Update the record of qubit positions for each iteration
                layout = best_layout

                if first_layer:
                    self.initial_layout = layout

                # Update the DAG
                dagcircuit_output.extend_back(
                    self._layer_update(i,
                                       first_layer,
                                       best_layout,
                                       best_depth,
                                       best_circuit,
                                       layerlist),
                    identity_wire_map)

                if first_layer:
                    first_layer = False

        # This is the final edgemap. We might use it to correctly replace
        # any measurements that needed to be removed earlier.
        logger.debug("mapper: self.initial_layout = %s", pformat(self.initial_layout))
        logger.debug("mapper: layout = %s", pformat(layout))
        last_edgemap = layout.combine_into_edge_map(self.initial_layout)
        logger.debug("mapper: last_edgemap = %s", pformat(last_edgemap))

        # If first_layer is still set, the circuit only has single-qubit gates
        # so we can use the initial layout to output the entire circuit
        # This code is dead due to changes to first_layer above.
        if first_layer:
            logger.debug("mapper: first_layer flag still set")
            layout = self.initial_layout
            for i, layer in enumerate(layerlist):
                edge_map = layout.combine_into_edge_map(self.initial_layout)
                dagcircuit_output.compose_back(layer["graph"], edge_map)

        return dagcircuit_output

def _layer_permutation(layer_partition, initial_layout, layout, qubit_subset,
                       coupling, trials, qregs, rng):
    """Find a swap circuit that implements a permutation for this layer.

    Args:
        layer_partition (list): The layer_partition is a list of (qu)bit
            lists and each qubit is a tuple (qreg, index).
        initial_layout (Layout): The initial layout passed.
        layout (Layout): The layout is a Layout object mapping virtual
            qubits in the input circuit to physical qubits in the coupling
            graph. It reflects the current positions of the data.
        qubit_subset (list): The qubit_subset is the set of qubits in
            the coupling graph that we have chosen to map into, as tuples
            (Register, index).
        coupling (CouplingMap): Directed graph representing a coupling map.
            This coupling map should be one that was provided to the
            stochastic mapper.
        trials (int): Number of attempts the randomized algorithm makes.
        qregs (OrderedDict): Ordered dict of registers from input DAG.
        rng (RandomState): Random number generator.

    Returns:
        Tuple: success_flag, best_circuit, best_depth, best_layout, trivial_flag

    Raises:
        TranspilerError: if anything went wrong.
     """
    logger.debug("layer_permutation: layer_partition = %s",
                 pformat(layer_partition))
    logger.debug("layer_permutation: layout = %s",
                 pformat(layout.get_virtual_bits()))
    logger.debug("layer_permutation: qubit_subset = %s",
                 pformat(qubit_subset))
    logger.debug("layer_permutation: trials = %s", trials)

    gates = []  # list of lists of tuples [[(register, index), ...], ...]
    for gate_args in layer_partition:
        if len(gate_args) > 2:
            raise TranspilerError("Layer contains > 2-qubit gates")
        elif len(gate_args) == 2:
            gates.append(tuple(gate_args))
    logger.debug("layer_permutation: gates = %s", pformat(gates))

    # Can we already apply the gates? If so, there is no work to do.
    dist = sum([coupling.distance(layout[g[0]], layout[g[1]])
                for g in gates])
    logger.debug("layer_permutation: distance = %s", dist)
    if dist == len(gates):
        logger.debug("layer_permutation: nothing to do")
        circ = DAGCircuit()
        for register in layout.get_virtual_bits().keys():
            if register[0] not in circ.qregs.values():
                circ.add_qreg(register[0])
        return True, circ, 0, layout, (not bool(gates))

    # Begin loop over trials of randomized algorithm
    num_qubits = len(layout)
    best_depth = inf  # initialize best depth
    best_edges = None  # best edges found
    best_circuit = None  # initialize best swap circuit
    best_layout = None  # initialize best final layout

    cdist2 = coupling._dist_matrix**2
    # Scaling matrix
    scale = np.zeros((num_qubits, num_qubits))

    int_qubit_subset = regtuple_to_numeric(qubit_subset, qregs)
    int_gates = gates_to_idx(gates, qregs)
    int_layout = nlayout_from_layout(layout, qregs, coupling.size())

    trial_circuit = DAGCircuit()  # SWAP circuit for this trial
    for register in layout.get_virtual_bits().keys():
        if register[0] not in trial_circuit.qregs.values():
            trial_circuit.add_qreg(register[0])

    slice_circuit = DAGCircuit()  # circuit for this swap slice
    for register in layout.get_virtual_bits().keys():
        if register[0] not in slice_circuit.qregs.values():
            slice_circuit.add_qreg(register[0])
    edges = np.asarray(coupling.get_edges(), dtype=np.int32).ravel()
    cdist = coupling._dist_matrix
    for trial in range(trials):
        logger.debug("layer_permutation: trial %s", trial)
        # This is one Trial --------------------------------------
        dist, optim_edges, trial_layout, depth_step = swap_trial(num_qubits, int_layout,
                                                                 int_qubit_subset,
                                                                 int_gates, cdist2,
                                                                 cdist, edges, scale,
                                                                 rng)

        logger.debug("layer_permutation: final distance for this trial = %s", dist)
        if dist == len(gates) and depth_step < best_depth:
            logger.debug("layer_permutation: got circuit with improved depth %s",
                         depth_step)
            best_edges = optim_edges
            best_layout = trial_layout
            best_depth = min(best_depth, depth_step)

        # Break out of trial loop if we found a depth 1 circuit
        # since we can't improve it further
        if best_depth == 1:
            break

    # If we have no best circuit for this layer, all of the
    # trials have failed
    if best_layout is None:
        logger.debug("layer_permutation: failed!")
        return False, None, None, None, False

    edgs = best_edges.edges()
    for idx in range(best_edges.size//2):
        slice_circuit.apply_operation_back(
            SwapGate(), [initial_layout[edgs[2*idx]], initial_layout[edgs[2*idx+1]]], [])
    trial_circuit.extend_back(slice_circuit)
    best_circuit = trial_circuit

    # Otherwise, we return our result for this layer
    logger.debug("layer_permutation: success!")
    best_lay = best_layout.to_layout(qregs)
    return True, best_circuit, best_depth, best_lay, False

def regtuple_to_numeric(items, qregs):
    """Takes (QuantumRegister, int) tuples and converts
    them into an integer array.

    Args:
        items (list): List of tuples of (QuantumRegister, int)
                      to convert.
        qregs (dict): List of )QuantumRegister, int) tuples.
    Returns:
        ndarray: Array of integers.

    """
    sizes = [qr.size for qr in qregs.values()]
    reg_idx = np.cumsum([0]+sizes)
    regint = {}
    for ind, qreg in enumerate(qregs.values()):
        regint[qreg] = ind
    out = np.zeros(len(items), dtype=np.int32)
    for idx, val in enumerate(items):
        out[idx] = reg_idx[regint[val[0]]]+val[1]
    return out

def gates_to_idx(gates, qregs):
    """Converts gate tuples into a nested list of integers.

    Args:
        gates (list): List of (QuantumRegister, int) pairs
                      representing gates.
        qregs (dict): List of )QuantumRegister, int) tuples.

    Returns:
        list: Nested list of integers for gates.
    """
    sizes = [qr.size for qr in qregs.values()]
    reg_idx = np.cumsum([0]+sizes)
    regint = {}
    for ind, qreg in enumerate(qregs.values()):
        regint[qreg] = ind
    out = np.zeros(2*len(gates), dtype=np.int32)
    for idx, gate in enumerate(gates):
        out[2*idx] = reg_idx[regint[gate[0][0]]]+gate[0][1]
        out[2*idx+1] = reg_idx[regint[gate[1][0]]]+gate[1][1]
    return out