Source code for qbraid.transpiler.conversions.braket.braket_to_cirq

# Copyright (C) 2024 qBraid
# Copyright (C) Unitary Fund
#
# This file is part of the qBraid-SDK.
#
# The qBraid-SDK is free software released under the GNU General Public License v3
# or later. You can redistribute and/or modify it under the terms of the GPL v3.
# See the LICENSE file in the project root or <https://www.gnu.org/licenses/gpl-3.0.html>.
#
# This file includes code adapted from Mitiq (https://github.com/unitaryfund/mitiq)
# with modifications by qBraid. The original copyright notice is included above.
# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3.

# qbraid: skip-header

"""
Module for converting Braket circuits to Cirq circuits

"""
from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np
from braket.circuits import Circuit as BKCircuit
from braket.circuits import Instruction as BKInstruction
from braket.circuits import gates as braket_gates
from braket.circuits import noises as braket_noise_gate

try:
    import cirq
except ImportError:  # pragma: no cover
    cirq = None

try:
    import cirq_ionq.ionq_native_gates as cirq_ionq_ops
except ImportError:  # pragma: no cover
    cirq_ionq_ops = None

from qbraid.programs.gate_model.cirq import CirqCircuit as QbraidCircuit
from qbraid.transpiler.annotations import weight
from qbraid.transpiler.exceptions import ProgramConversionError

if TYPE_CHECKING:
    import cirq.circuits as cirq_circuits
    import cirq.devices as cirq_devices
    import cirq.ops as cirq_ops


def _give_cirq_gate_name(gate: cirq_ops.Gate, name: str, n_qubits: int) -> cirq_ops.Gate:
    def _circuit_diagram_info_(args):  # pylint: disable=unused-argument
        return name, *(name,) * (n_qubits - 1)

    gate._circuit_diagram_info_ = _circuit_diagram_info_


def matrix_to_cirq_gate(matrix: np.ndarray) -> cirq_ops.MatrixGate:
    """Return cirq matrix gate given unitary"""
    n_qubits = int(np.log2(len(matrix)))
    unitary_gate = cirq.MatrixGate(matrix)
    _give_cirq_gate_name(unitary_gate, "U", n_qubits)
    return unitary_gate


def braket_gate_to_matrix(gate: braket_gates.Unitary) -> np.ndarray:
    """Return the matrix representation of a Braket gate."""
    matrix = gate.to_matrix()
    unitary_gate = braket_gates.Unitary(matrix)
    nqubits = int(np.log2(len(matrix)))
    qubits = list(range(nqubits)) if nqubits > 1 else 0
    bk_circuit = BKCircuit([BKInstruction(unitary_gate, qubits)])
    return bk_circuit.to_unitary()


[docs] @weight(1) def braket_to_cirq(circuit: BKCircuit) -> cirq_circuits.Circuit: """Returns a Cirq circuit equivalent to the input Braket circuit. Note: The returned Cirq circuit acts on cirq.LineQubit's with indices equal to the qubit indices of the Braket circuit. Args: circuit: Braket circuit to convert to a Cirq circuit. Returns: Cirq circuit equivalent to the input Braket circuit. """ bk_qubits = [int(q) for q in circuit.qubits] cirq_qubits = [cirq.LineQubit(x) for x in bk_qubits] qubit_mapping = {q: cirq_qubits[i] for i, q in enumerate(bk_qubits)} circuit = cirq.Circuit( _from_braket_instruction(instr, qubit_mapping) for instr in circuit.instructions ) return QbraidCircuit.align_final_measurements(circuit)
def _from_braket_instruction( instr: BKInstruction, qubit_mapping: dict[int, cirq_devices.LineQubit] ) -> list[cirq_ops.Operation]: """Converts the braket instruction to an equivalent Cirq operation or list of Cirq operations. Args: instr: Braket instruction to convert. qubit_mapping: Braket qubit indicies mapped to indexed Cirq LineQubits Raises: ValueError: If the instruction cannot be converted to Cirq. ProgramConversionError: If error raise during conversion. """ nqubits = len(instr.target) BK_qubits = [int(q) for q in instr.target] qubits = [qubit_mapping[x] for x in BK_qubits] if str(instr.operator) == "Measure": return [cirq.ops.MeasurementGate(num_qubits=nqubits).on(*qubits)] try: if nqubits == 1: return _from_one_qubit_braket_instruction(instr, qubits) if nqubits == 2: return _from_two_qubit_braket_instruction(instr, qubits) if nqubits == 3: if isinstance(instr.operator, braket_gates.CCNot): return [cirq.ops.TOFFOLI.on(*qubits)] if isinstance(instr.operator, braket_gates.CSwap): return [cirq.ops.FREDKIN.on(*qubits)] try: matrix = braket_gate_to_matrix(instr.operator) return [cirq.ops.MatrixGate(matrix).on(*qubits)] except (ValueError, TypeError) as err: raise ProgramConversionError( f"Unable to convert the instruction {instr} to Cirq." ) from err # Unknown instructions. raise ProgramConversionError( f"Unable to convert to Cirq due to unrecognized \ instruction: {instr}." ) except Exception as err: raise ProgramConversionError( f"qBraid transpiler doesn't support operator {instr.operator}" ) from err def _from_one_qubit_braket_instruction( instr: BKInstruction, qubits: list[cirq_devices.LineQubit] ) -> list[cirq_ops.Operation]: """Converts the one-qubit Braket instruction to Cirq operation(s). Args: instr: One-qubit Braket instruction to convert. qubits: Cirq LineQubit list indexed according to Braket instruction Raises: ValueError: If the instruction cannot be converted to Cirq. """ gate = instr.operator # One-qubit non-parameterized gates. if isinstance(gate, braket_gates.I): return [cirq.ops.I.on(*qubits)] if isinstance(gate, braket_gates.X): return [cirq.ops.X.on(*qubits)] if isinstance(gate, braket_gates.Y): return [cirq.ops.Y.on(*qubits)] if isinstance(gate, braket_gates.Z): return [cirq.ops.Z.on(*qubits)] if isinstance(gate, braket_gates.H): return [cirq.ops.H.on(*qubits)] if isinstance(gate, braket_gates.S): return [cirq.ops.S.on(*qubits)] if isinstance(gate, braket_gates.Si): return [cirq.protocols.inverse(cirq.ops.S.on(*qubits))] if isinstance(gate, braket_gates.T): return [cirq.ops.T.on(*qubits)] if isinstance(gate, braket_gates.Ti): return [cirq.protocols.inverse(cirq.ops.T.on(*qubits))] if isinstance(gate, braket_gates.V): return [cirq.ops.X.on(*qubits) ** 0.5] if isinstance(gate, braket_gates.Vi): return [cirq.ops.X.on(*qubits) ** -0.5] # One-qubit parameterized gates. if isinstance(gate, braket_gates.Rx): return [cirq.ops.rx(gate.angle).on(*qubits)] if isinstance(gate, braket_gates.Ry): return [cirq.ops.ry(gate.angle).on(*qubits)] if isinstance(gate, braket_gates.Rz): return [cirq.ops.rz(gate.angle).on(*qubits)] if isinstance(gate, braket_gates.PhaseShift): return [cirq.ops.Z.on(*qubits) ** (gate.angle / np.pi)] # One-qubit parameterized IonQ gates if cirq_ionq_ops and isinstance(gate, (braket_gates.GPi, braket_gates.GPi2)): phi = gate.angle / (2 * np.pi) gate_class = ( cirq_ionq_ops.GPIGate if isinstance(gate, braket_gates.GPi) else cirq_ionq_ops.GPI2Gate ) return [gate_class(phi=phi).on(*qubits)] # One-qubit Noise gates. if isinstance(gate, braket_noise_gate.BitFlip): return [cirq.ops.BitFlipChannel(gate.probability).on(*qubits)] if isinstance(gate, braket_noise_gate.PhaseFlip): return [cirq.ops.PhaseFlipChannel(gate.probability).on(*qubits)] if isinstance(gate, braket_noise_gate.Depolarizing): return [cirq.ops.DepolarizingChannel(gate.probability).on(*qubits)] if isinstance(gate, braket_noise_gate.AmplitudeDamping): return [cirq.ops.AmplitudeDampingChannel(gate.gamma).on(*qubits)] if isinstance(gate, braket_noise_gate.GeneralizedAmplitudeDamping): return [ cirq.ops.GeneralizedAmplitudeDampingChannel(gate.probability, gate.gamma).on(*qubits) ] if isinstance(gate, braket_noise_gate.PhaseDamping): return [cirq.ops.PhaseDampingChannel(gate.gamma).on(*qubits)] try: matrix = braket_gate_to_matrix(gate) return [cirq.ops.MatrixGate(matrix).on(*qubits)] except (ValueError, TypeError) as err: raise ValueError(f"Unable to convert the instruction {instr} to Cirq.") from err def _from_two_qubit_braket_instruction( instr: BKInstruction, qubits: list[cirq_devices.LineQubit] ) -> list[cirq_ops.Operation]: """Converts the two-qubit braket instruction to Cirq. Args: instr: Two-qubit Braket instruction to convert. qubits: Cirq LineQubit list indexed according to Braket instruction Raises: ValueError: If the instruction cannot be converted to Cirq. """ gate = instr.operator # Two-qubit non-parameterized gates. if isinstance(gate, braket_gates.CNot): return [cirq.ops.CNOT.on(*qubits)] if isinstance(gate, braket_gates.Swap): return [cirq.ops.SWAP.on(*qubits)] if isinstance(gate, braket_gates.ISwap): return [cirq.ops.ISWAP.on(*qubits)] if isinstance(gate, braket_gates.CZ): return [cirq.ops.CZ.on(*qubits)] if isinstance(gate, braket_gates.CY): return [ cirq.protocols.inverse(cirq.ops.S.on(qubits[1])), cirq.ops.CNOT.on(*qubits), cirq.ops.S.on(qubits[1]), ] # Two-qubit parameterized gates. if isinstance(gate, braket_gates.CPhaseShift): return [cirq.ops.CZ.on(*qubits) ** (gate.angle / np.pi)] if isinstance(gate, braket_gates.CPhaseShift00): return [ cirq.ops.XX(*qubits), cirq.ops.CZ.on(*qubits) ** (gate.angle / np.pi), cirq.ops.XX(*qubits), ] if isinstance(gate, braket_gates.CPhaseShift01): return [ cirq.ops.X(qubits[0]), cirq.ops.CZ.on(*qubits) ** (gate.angle / np.pi), cirq.ops.X(qubits[0]), ] if isinstance(gate, braket_gates.CPhaseShift10): return [ cirq.ops.X(qubits[1]), cirq.ops.CZ.on(*qubits) ** (gate.angle / np.pi), cirq.ops.X(qubits[1]), ] if isinstance(gate, braket_gates.PSwap): return [ cirq.ops.SWAP.on(*qubits), cirq.ops.CNOT.on(*qubits), cirq.ops.Z.on(qubits[1]) ** (gate.angle / np.pi), cirq.ops.CNOT.on(*qubits), ] if isinstance(gate, braket_gates.XX): return [cirq.ops.XXPowGate(exponent=gate.angle / np.pi, global_shift=-0.5).on(*qubits)] if isinstance(gate, braket_gates.YY): return [cirq.ops.YYPowGate(exponent=gate.angle / np.pi, global_shift=-0.5).on(*qubits)] if isinstance(gate, braket_gates.ZZ): return [cirq.ops.ZZPowGate(exponent=gate.angle / np.pi, global_shift=-0.5).on(*qubits)] if isinstance(gate, braket_gates.XY): return [cirq.ops.ISwapPowGate(exponent=gate.angle / np.pi).on(*qubits)] # Two-qubit noise gates. if isinstance(gate, braket_noise_gate.Kraus): return [cirq.ops.KrausChannel(gate._matrices).on(*qubits)] if isinstance(gate, braket_noise_gate.TwoQubitDepolarizing): return [cirq.ops.DepolarizingChannel(gate.probability, n_qubits=2).on(*qubits)] # Two-qubit two-parameters IonQ gates. if cirq_ionq_ops and isinstance(gate, braket_gates.MS): return [ cirq_ionq_ops.MSGate( phi0=gate.angle_1 / (2 * np.pi), phi1=gate.angle_2 / (2 * np.pi), theta=gate.angle_3 / (2 * np.pi), ).on(*qubits) ] try: matrix = braket_gate_to_matrix(gate) unitary_gate = matrix_to_cirq_gate(matrix) return [unitary_gate.on(*qubits)] except (ValueError, TypeError) as err: raise ValueError(f"Unable to convert the instruction {instr} to Cirq.") from err