Source code for qiskit_research.utils.gate_decompositions

# (C) Copyright IBM 2022.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Gate decompositions."""

from __future__ import annotations

from collections.abc import Iterator

from qiskit import QuantumRegister
from qiskit.circuit import ControlledGate, Gate, Qubit
from qiskit.circuit.library import (
    CXGate,
    HGate,
    RXGate,
    RZGate,
    RZXGate,
    SdgGate,
    SGate,
    XGate,
    XXMinusYYGate,
    XXPlusYYGate,
)
from qiskit.dagcircuit import DAGCircuit
from qiskit.exceptions import QiskitError
from qiskit.providers.backend import Backend
from qiskit.pulse import ControlChannel, InstructionScheduleMap, Play
from qiskit.qasm import pi
from qiskit.transpiler.basepasses import TransformationPass

from .gates import SECRGate


def cr_forward_direction(
    control: int,
    target: int,
    inst_sched_map: InstructionScheduleMap,
    ctrl_chans: dict,
) -> bool:
    """
    Determines if the direction of cross resonance is forward (True), applied on control qubit qc or
    reverse (False), applied to target qubit qt.
    """
    if inst_sched_map.has("cx", qubits=[control, target]):
        cr_sched = inst_sched_map.get("cx", qubits=[control, target])
    elif inst_sched_map.has("ecr", qubits=[control, target]):
        return True
    elif inst_sched_map.has("ecr", qubits=[target, control]):
        return False
    else:
        raise QiskitError(
            f"Native direction cannot be determined between qubits {control} and {target}."
        )
    cr_ctrl_chan = (
        cr_sched.filter(
            channels=[
                ControlChannel(idx)
                for idx in range(len(inst_sched_map.qubits_with_instruction("cx")))
            ],
            instruction_types=Play,
        )
        .instructions[0][1]
        .channel
    )
    forward_ctrl_chan = ctrl_chans[(control, target)][0]
    reverse_ctrl_chan = ctrl_chans[(target, control)][0]

    if cr_ctrl_chan == forward_ctrl_chan:
        return True
    if cr_ctrl_chan == reverse_ctrl_chan:
        return False

    raise ValueError(f"Qubits {control} and {target} are not a cross resonance pair.")


[docs] class RZXtoEchoedCR(TransformationPass): """ Class for the RZXGate to echoed cross resonance gate pass. The RZXGate is equivalent to the SECR gate plus a second XGate on the control qubit to return it to the initial state. See: https://arxiv.org/abs/1603.04821 """ def __init__( self, backend: Backend, ): super().__init__() self._inst_map = backend.defaults().instruction_schedule_map self._ctrl_chans = backend.configuration().control_channels
[docs] def run( self, dag: DAGCircuit, ) -> DAGCircuit: for rzx_run in dag.collect_runs(["rzx"]): control, _ = dag.find_bit(rzx_run[0].qargs[0]) target, _ = dag.find_bit(rzx_run[0].qargs[1]) cr_forward_dir = cr_forward_direction( control, target, self._inst_map, self._ctrl_chans ) for node in rzx_run: mini_dag = DAGCircuit() q0, q1 = QuantumRegister(2) mini_dag.add_qubits([q0, q1]) rzx_angle = node.op.params[0] if cr_forward_dir: mini_dag.apply_operation_back(SECRGate(rzx_angle), [q0, q1]) mini_dag.apply_operation_back(XGate(), [q0]) else: mini_dag.apply_operation_back(HGate(), [q0]) mini_dag.apply_operation_back(HGate(), [q1]) mini_dag.apply_operation_back(SECRGate(rzx_angle), [q1, q0]) mini_dag.apply_operation_back(XGate(), [q1]) mini_dag.apply_operation_back(HGate(), [q0]) mini_dag.apply_operation_back(HGate(), [q1]) dag.substitute_node_with_dag(node, mini_dag) return dag
class ControlledRZZToCX(TransformationPass): """Transformation pass to decompose Controlled RZZGate to CXGate.""" def _decomposition( self, register: QuantumRegister, gate: ControlledGate, ) -> Iterator[tuple[Gate, tuple[Qubit, ...]]]: a, b, c = register (theta,) = gate.params yield CXGate(), (b, c) yield RZGate(theta).control(1), (a, c) yield CXGate(), (b, c) def run( self, dag: DAGCircuit, ) -> DAGCircuit: for run in dag.collect_runs(["crzz"]): for node in run: mini_dag = DAGCircuit() register = QuantumRegister(3) mini_dag.add_qreg(register) for instr, qargs in self._decomposition(register, node.op): mini_dag.apply_operation_back(instr, qargs) dag.substitute_node_with_dag(node, mini_dag) return dag
[docs] class XXPlusYYtoRZX(TransformationPass): """Transformation pass to decompose XXPlusYYGate to RZXGate.""" def _decomposition( self, register: QuantumRegister, gate: XXPlusYYGate, ) -> Iterator[tuple[Gate, tuple[Qubit, ...]]]: a, b = register theta, beta = gate.params yield RZGate(-beta), (b,) yield HGate(), (a,) yield HGate(), (b,) yield RZGate(-0.5 * pi), (b,) yield RXGate(-0.5 * pi), (b,) yield RZGate(-0.5 * pi), (b,) yield RZXGate(0.5 * theta), (a, b) yield RXGate(0.5 * theta), (b,) yield RZGate(-0.5 * pi), (b,) yield RXGate(-0.5 * pi), (b,) yield RZGate(-0.5 * pi), (b,) yield RZGate(-0.5 * theta), (b,) yield RZGate(0.5 * pi), (a,) yield HGate(), (a,) yield RZGate(0.5 * pi), (b,) yield HGate(), (b,) yield RZGate(-0.5 * pi), (b,) yield RXGate(-0.5 * pi), (b,) yield RZGate(-0.5 * pi), (b,) yield RZXGate(0.5 * theta), (a, b) yield RXGate(0.5 * theta), (b,) yield RZGate(-0.5 * pi), (b,) yield RXGate(-0.5 * pi), (b,) yield RZGate(-0.5 * pi), (b,) yield RZGate(-0.5 * theta), (b,) yield HGate(), (a,) yield RZGate(-0.5 * pi), (a,) yield HGate(), (a,) yield HGate(), (b,) yield RZGate(-0.5 * pi), (b,) yield HGate(), (b,) yield RZGate(beta), (b,)
[docs] def run( self, dag: DAGCircuit, ) -> DAGCircuit: for run in dag.collect_runs(["xx_plus_yy"]): for node in run: mini_dag = DAGCircuit() register = QuantumRegister(2) mini_dag.add_qreg(register) for instr, qargs in self._decomposition(register, node.op): mini_dag.apply_operation_back(instr, qargs) dag.substitute_node_with_dag(node, mini_dag) return dag
[docs] class XXMinusYYtoRZX(TransformationPass): """Transformation pass to decompose XXMinusYYGate to RZXGate.""" def _decomposition( self, register: QuantumRegister, gate: XXMinusYYGate, ) -> Iterator[tuple[Gate, tuple[Qubit, ...]]]: a, b = register theta, beta = gate.params yield RZGate(-beta), (b,) yield HGate(), (a,) yield HGate(), (b,) yield RZGate(-0.5 * pi), (b,) yield RXGate(-0.5 * pi), (b,) yield RZGate(-0.5 * pi), (b,) yield RZXGate(0.5 * theta), (a, b) yield RXGate(-0.5 * theta), (b,) yield RZGate(-0.5 * pi), (b,) yield RXGate(-0.5 * pi), (b,) yield RZGate(-0.5 * pi), (b,) yield RZGate(0.5 * theta), (b,) yield RZGate(0.5 * pi), (a,) yield HGate(), (a,) yield RZGate(0.5 * pi), (b,) yield HGate(), (b,) yield RZGate(-0.5 * pi), (b,) yield RXGate(-0.5 * pi), (b,) yield RZGate(-0.5 * pi), (b,) yield RZXGate(-0.5 * theta), (a, b) yield RXGate(0.5 * theta), (b,) yield RZGate(-0.5 * pi), (b,) yield RXGate(-0.5 * pi), (b,) yield RZGate(-0.5 * pi), (b,) yield RZGate(-0.5 * theta), (b,) yield HGate(), (a,) yield RZGate(-0.5 * pi), (a,) yield HGate(), (a,) yield HGate(), (b,) yield RZGate(-0.5 * pi), (b,) yield HGate(), (b,) yield RZGate(beta), (b,)
[docs] def run( self, dag: DAGCircuit, ) -> DAGCircuit: for run in dag.collect_runs(["xx_minus_yy"]): for node in run: mini_dag = DAGCircuit() register = QuantumRegister(2) mini_dag.add_qreg(register) for instr, qargs in self._decomposition(register, node.op): mini_dag.apply_operation_back(instr, qargs) dag.substitute_node_with_dag(node, mini_dag) return dag
[docs] class RZXWeylDecomposition(TransformationPass): """ Decompose XX, YY, ZZ rotation gates using the Weyl Chamber decomposition, this version accepts Parameter values. See https://arxiv.org/abs/2105.01063 """
[docs] def run(self, dag: DAGCircuit) -> DAGCircuit: for run in dag.collect_runs(["rxx", "ryy", "rzz"]): for node in run: mini_dag = DAGCircuit() register = QuantumRegister(2) mini_dag.add_qreg(register) angle = node.op.params[0] if node.op.name == "rxx": mini_dag.apply_operation_back(HGate(), [register[0]]) mini_dag.apply_operation_back( RZXGate(angle), [register[0], register[1]] ) mini_dag.apply_operation_back(HGate(), [register[0]]) elif node.op.name == "ryy": mini_dag.apply_operation_back(SdgGate(), [register[0]]) mini_dag.apply_operation_back(SdgGate(), [register[1]]) mini_dag.apply_operation_back(HGate(), [register[0]]) mini_dag.apply_operation_back( RZXGate(angle), [register[0], register[1]] ) mini_dag.apply_operation_back(HGate(), [register[0]]) mini_dag.apply_operation_back(SGate(), [register[0]]) mini_dag.apply_operation_back(SGate(), [register[1]]) elif node.op.name == "rzz": mini_dag.apply_operation_back(HGate(), [register[1]]) mini_dag.apply_operation_back( RZXGate(angle), [register[0], register[1]] ) mini_dag.apply_operation_back(HGate(), [register[1]]) dag.substitute_node_with_dag(node, mini_dag) return dag