Source code for qutip_qip.qiskit.converter

"""Conversion of circuits from qiskit to qutip_qip."""

from qutip_qip.circuit import QubitCircuit
from qiskit.quantum_info import Operator
import numpy as np
from qutip import Qobj
from typing import Union
import qiskit
from qiskit.circuit import QuantumCircuit

_map_gates = {
    "p": "PHASEGATE",
    "x": "X",
    "y": "Y",
    "z": "Z",
    "h": "SNOT",
    "s": "S",
    "t": "T",
    "rx": "RX",
    "ry": "RY",
    "rz": "RZ",
    "swap": "SWAP",
    "u": "QASMU",
}

_map_controlled_gates = {
    "cx": "CX",
    "cy": "CY",
    "cz": "CZ",
    "crx": "CRX",
    "cry": "CRY",
    "crz": "CRZ",
    "cp": "CPHASE",
}

_ignore_gates = ["id", "barrier"]


def _make_user_gate(
    unitary: np.ndarray, qiskit_instruction: qiskit.circuit.Instruction
):
    """
    Returns a user defined gate from a unitary matrix.

    Parameters
    ----------
    unitary : numpy.ndarray
        The unitary matrix describing the gate.

    qiskit_instruction : qiskit.circuit.Instruction
        Qiskit instruction containing info about the gate.
    """

    def user_gate():
        return Qobj(
            unitary,
            dims=[
                [2] * qiskit_instruction.num_qubits,
                [2] * qiskit_instruction.num_qubits,
            ],
        )

    return user_gate


def _get_qutip_index(bit_index: Union[int, list], total_bits: int) -> int:
    """
    Map the bit index from qiskit to qutip.

    Note
    ----
    When we convert a circuit from qiskit to qutip,
    the 0st bit is mapped to the (n-1)th bit and so on.


    """

    if isinstance(bit_index, list):
        return [_get_qutip_index(bit, total_bits) for bit in bit_index]
    else:
        return total_bits - 1 - bit_index


def _get_mapped_bits(bits: Union[list, tuple], bit_map: dict) -> list:
    return [bit_map[bit] for bit in bits]


[docs]def convert_qiskit_circuit( qiskit_circuit: QuantumCircuit, allow_custom_gate=True ) -> QubitCircuit: """ Convert a :class:`qiskit.circuit.QuantumCircuit` object from ``qiskit`` to ``qutip_qip``'s :class:`.QubitCircuit`. Parameters ---------- qiskit_circuit : :class:`qiskit.circuit.QuantumCircuit` The :class:`qiskit.circuit.QuantumCircuit` object to be converted to :class:`QubitCircuit`. allow_custom_gate : bool If False, this function will raise an error if gate conversion is done using a custom gate's unitary matrix. Returns ------- :class:`.QubitCircuit` The converted circuit in qutip_qip's :class:`.QubitCircuit` format. """ qubit_map = {} for qiskit_index, qubit in enumerate(qiskit_circuit.qubits): qubit_map[qubit] = _get_qutip_index( qiskit_index, total_bits=qiskit_circuit.num_qubits ) clbit_map = {} for qiskit_index, clbit in enumerate(qiskit_circuit.clbits): clbit_map[clbit] = _get_qutip_index( qiskit_index, total_bits=qiskit_circuit.num_clbits ) qutip_circuit = QubitCircuit( N=qiskit_circuit.num_qubits, num_cbits=qiskit_circuit.num_clbits ) qutip_circuit.name = qiskit_circuit.name for qiskit_gate in qiskit_circuit.data: # qiskit_gate stores info about the gate, target # qubits and classical bits (for measurements) qiskit_instruction = qiskit_gate[0] qiskit_qregs = qiskit_gate[1] qiskit_cregs = qiskit_gate[2] # setting the gate argument values according # to the required qutip_qip format if not qiskit_instruction.params: arg_value = None elif len(qiskit_instruction.params) == 1: arg_value = qiskit_instruction.params[0] else: arg_value = qiskit_instruction.params # add the corresponding gate in qutip_qip if qiskit_instruction.name in _map_gates.keys(): qutip_circuit.add_gate( _map_gates[qiskit_instruction.name], targets=_get_mapped_bits(qiskit_qregs, bit_map=qubit_map), arg_value=arg_value, ) elif qiskit_instruction.name in _map_controlled_gates.keys(): qutip_circuit.add_gate( _map_controlled_gates[qiskit_instruction.name], # The 0th bit is the control bit in qiskit by # convention, in case of a controlled operation controls=_get_mapped_bits( [qiskit_qregs[0]], bit_map=qubit_map ), targets=_get_mapped_bits(qiskit_qregs[1:], bit_map=qubit_map), arg_value=arg_value, ) elif qiskit_instruction.name == "measure": qutip_circuit.add_measurement( "measure", targets=_get_mapped_bits(qiskit_qregs, bit_map=qubit_map), classical_store=clbit_map[qiskit_cregs[0]], ) elif qiskit_instruction.name in _ignore_gates: pass else: if not allow_custom_gate: raise RuntimeError( f"{qiskit_instruction.name} is not a \ gate in qutip_qip. To simulate this gate using it's corresponding \ unitary matrix, set allow_custom_gate=True." ) unitary = np.array(Operator(qiskit_instruction)) qutip_circuit.user_gates[qiskit_instruction.name] = ( _make_user_gate(unitary, qiskit_instruction) ) qutip_circuit.add_gate( qiskit_instruction.name, targets=_get_mapped_bits(qiskit_qregs, bit_map=qubit_map), ) return qutip_circuit