Skip to content

cnot_decomposer

CNOTDecomposer

Bases: Decomposer

Decomposes 2-qubit controlled unitary gates to CNOT + Rz/Ry.

Applying single-qubit gate fusion after this pass might be beneficial.

Based on the CNOT decomposition described in Quantum Gates by G.E. Crooks (2024), Section 7.5.

Source code in opensquirrel/passes/decomposer/cnot_decomposer.py
class CNOTDecomposer(Decomposer):
    """Decomposes 2-qubit controlled unitary gates to CNOT + Rz/Ry.

    Applying single-qubit gate fusion after this pass might be beneficial.

    Based on the CNOT decomposition described in
    [Quantum Gates by G.E. Crooks (2024), Section 7.5](https://threeplusone.com/pubs/on_gates.pdf).
    """

    def decompose(self, gate: Gate) -> list[Gate]:
        """Decomposes a controlled two-qubit gate into a sequence of (at most 2) CNOT gates and
        single-qubit gates. It decomposes the CR, CRk, and CZ controlled two-qubit gates.

        Note:
            The SWAP gate is not a controlled two-qubit gate and is not decomposed by this pass.
            To decompose SWAP gates, use the
            [SWAP2CNOTDecomposer][opensquirrel.passes.decomposer.swap2cnot_decomposer.SWAP2CNOTDecomposer]
            or the [SWAP2CZDecomposer][opensquirrel.passes.decomposer.swap2cz_decomposer.SWAP2CZDecomposer].

        Args:
            gate (Gate): Two-qubit controlled gate to decompose.

        Returns:
            A sequence of (at most 2) CNOT gates and single-qubit gates.

        """
        if not isinstance(gate, TwoQubitGate) or not gate.controlled:
            return [gate]

        control_qubit = gate.qubit0
        target_qubit = gate.qubit1
        target_gate = SingleQubitGate(qubit=target_qubit, gate_semantic=gate.controlled.target_bsr)
        # Perform ZYZ decomposition on the target gate.
        # This gives us an ABC decomposition (U = AXBXC, ABC = I) of the target gate.
        # See https://threeplusone.com/pubs/on_gates.pdf

        # Try special case first, see https://arxiv.org/pdf/quant-ph/9503016.pdf lemma 5.5
        controlled_rotation_times_x = target_gate * X(target_qubit)
        theta0_with_x, theta1_with_x, theta2_with_x = ZYZDecomposer()._determine_rotation_angles(  # noqa: SLF001
            controlled_rotation_times_x.bsr.axis,
            controlled_rotation_times_x.bsr.angle,
        )
        if abs((theta0_with_x - theta2_with_x) % (2 * pi)) < ATOL:
            # The decomposition can use a single CNOT according to the lemma.
            A = [Ry(target_qubit, -theta1_with_x / 2), Rz(target_qubit, -theta2_with_x)]  # noqa: N806
            B = [Rz(target_qubit, theta2_with_x), Ry(target_qubit, theta1_with_x / 2)]  # noqa: N806
            return filter_out_identities(
                [
                    *B,
                    CNOT(control_qubit, target_qubit),
                    *A,
                    Rz(control_qubit, target_gate.bsr.phase - pi / 2),
                ],
            )

        theta0, theta1, theta2 = ZYZDecomposer()._determine_rotation_angles(target_gate.bsr.axis, target_gate.bsr.angle)  # noqa: SLF001

        A = [Ry(target_qubit, theta1 / 2), Rz(target_qubit, theta2)]  # noqa: N806
        B = [Rz(target_qubit, -(theta0 + theta2) / 2), Ry(target_qubit, -theta1 / 2)]  # noqa: N806
        C = [Rz(target_qubit, (theta0 - theta2) / 2)]  # noqa: N806

        return filter_out_identities(
            [
                *C,
                CNOT(control_qubit, target_qubit),
                *B,
                CNOT(control_qubit, target_qubit),
                *A,
                Rz(control_qubit, target_gate.bsr.phase),
            ],
        )

decompose

decompose(gate: Gate) -> list[Gate]

Decomposes a controlled two-qubit gate into a sequence of (at most 2) CNOT gates and single-qubit gates. It decomposes the CR, CRk, and CZ controlled two-qubit gates.

Note

The SWAP gate is not a controlled two-qubit gate and is not decomposed by this pass. To decompose SWAP gates, use the SWAP2CNOTDecomposer or the SWAP2CZDecomposer.

Parameters:

Name Type Description Default
gate Gate

Two-qubit controlled gate to decompose.

required

Returns:

Type Description
list[Gate]

A sequence of (at most 2) CNOT gates and single-qubit gates.

Source code in opensquirrel/passes/decomposer/cnot_decomposer.py
def decompose(self, gate: Gate) -> list[Gate]:
    """Decomposes a controlled two-qubit gate into a sequence of (at most 2) CNOT gates and
    single-qubit gates. It decomposes the CR, CRk, and CZ controlled two-qubit gates.

    Note:
        The SWAP gate is not a controlled two-qubit gate and is not decomposed by this pass.
        To decompose SWAP gates, use the
        [SWAP2CNOTDecomposer][opensquirrel.passes.decomposer.swap2cnot_decomposer.SWAP2CNOTDecomposer]
        or the [SWAP2CZDecomposer][opensquirrel.passes.decomposer.swap2cz_decomposer.SWAP2CZDecomposer].

    Args:
        gate (Gate): Two-qubit controlled gate to decompose.

    Returns:
        A sequence of (at most 2) CNOT gates and single-qubit gates.

    """
    if not isinstance(gate, TwoQubitGate) or not gate.controlled:
        return [gate]

    control_qubit = gate.qubit0
    target_qubit = gate.qubit1
    target_gate = SingleQubitGate(qubit=target_qubit, gate_semantic=gate.controlled.target_bsr)
    # Perform ZYZ decomposition on the target gate.
    # This gives us an ABC decomposition (U = AXBXC, ABC = I) of the target gate.
    # See https://threeplusone.com/pubs/on_gates.pdf

    # Try special case first, see https://arxiv.org/pdf/quant-ph/9503016.pdf lemma 5.5
    controlled_rotation_times_x = target_gate * X(target_qubit)
    theta0_with_x, theta1_with_x, theta2_with_x = ZYZDecomposer()._determine_rotation_angles(  # noqa: SLF001
        controlled_rotation_times_x.bsr.axis,
        controlled_rotation_times_x.bsr.angle,
    )
    if abs((theta0_with_x - theta2_with_x) % (2 * pi)) < ATOL:
        # The decomposition can use a single CNOT according to the lemma.
        A = [Ry(target_qubit, -theta1_with_x / 2), Rz(target_qubit, -theta2_with_x)]  # noqa: N806
        B = [Rz(target_qubit, theta2_with_x), Ry(target_qubit, theta1_with_x / 2)]  # noqa: N806
        return filter_out_identities(
            [
                *B,
                CNOT(control_qubit, target_qubit),
                *A,
                Rz(control_qubit, target_gate.bsr.phase - pi / 2),
            ],
        )

    theta0, theta1, theta2 = ZYZDecomposer()._determine_rotation_angles(target_gate.bsr.axis, target_gate.bsr.angle)  # noqa: SLF001

    A = [Ry(target_qubit, theta1 / 2), Rz(target_qubit, theta2)]  # noqa: N806
    B = [Rz(target_qubit, -(theta0 + theta2) / 2), Ry(target_qubit, -theta1 / 2)]  # noqa: N806
    C = [Rz(target_qubit, (theta0 - theta2) / 2)]  # noqa: N806

    return filter_out_identities(
        [
            *C,
            CNOT(control_qubit, target_qubit),
            *B,
            CNOT(control_qubit, target_qubit),
            *A,
            Rz(control_qubit, target_gate.bsr.phase),
        ],
    )