Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion INTEGRATIONS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ cirq
pennylane
pyquil
qibo
qiskit
qiskit
cudaq
11 changes: 11 additions & 0 deletions mitiq/interface/conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ def convert_to_mitiq(circuit: QPROGRAM) -> tuple[cirq.Circuit, str]:
input_circuit_type = "qibo"
conversion_function = from_qibo

elif "cudaq" in package:
from mitiq.interface.mitiq_cudaq.conversions import from_cudaq

input_circuit_type = "cudaq"
conversion_function = from_cudaq

elif package in TO_MITIQ_DICT:
input_circuit_type = package
conversion_function = TO_MITIQ_DICT[package]
Expand Down Expand Up @@ -188,6 +194,11 @@ def convert_from_mitiq(
def conversion_function(circ: cirq.Circuit) -> cirq.Circuit:
return circ

elif conversion_type == "cudaq":
from mitiq.interface.mitiq_cudaq.conversions import to_cudaq

conversion_function = to_cudaq

else:
raise UnsupportedCircuitError(
f"Conversion to circuit type {conversion_type} is unsupported."
Expand Down
9 changes: 9 additions & 0 deletions mitiq/interface/mitiq_cudaq/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (C) Unitary Foundation
#
# This source code is licensed under the GPL license (v3) found in the
# LICENSE file in the root directory of this source tree.

from mitiq.interface.mitiq_cudaq.conversions import (
from_cudaq,
to_cudaq,
)
106 changes: 106 additions & 0 deletions mitiq/interface/mitiq_cudaq/conversions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Copyright (C) Unitary Foundation
#
# This source code is licensed under the GPL license (v3) found in the
# LICENSE file in the root directory of this source tree.

"""Functions to convert between Mitiq's internal circuit representation and
Cuda-Quantum's circuit representation.
"""

import cudaq
import numpy as np
from cirq import Circuit
from cudaq import PyKernel
from qbraid.transpiler.conversions.qasm2 import qasm2_to_cirq, qasm2_to_qiskit
from qbraid.transpiler.conversions.qiskit import qiskit_to_qasm2
from qiskit import QuantumCircuit, transpile

from mitiq.interface.mitiq_qiskit import to_qiskit


def from_cudaq(kernel: PyKernel) -> Circuit:
"""Convert a cudaq PyKernel to a cirq Circuit via OpenQASM 3.

Args:
kernel (PyKernel): The cudaq PyKernel to convert.

Returns:
Circuit: The converted cirq Circuit.
"""
cudaq_qasm = cudaq.translate(kernel, format="openqasm2")

# The register naming convention used by `cudaq.translate` for qasm
# causes certain equality checks like `mitiq.utils._equal` to fail
# despite identical circuits
# We resolve this by adding the circuit to an empty qiskit circuit
qiskit_qc = qasm2_to_qiskit(cudaq_qasm)
qiskit_corrected = QuantumCircuit(
qiskit_qc.num_qubits, qiskit_qc.num_clbits
)
qiskit_corrected.compose(qiskit_qc, inplace=True)

cirq_qc = qasm2_to_cirq(qiskit_to_qasm2(qiskit_corrected))

# To avoid failing `cirq.has_stabilizer_effects` due to
# floating point changes introduced by `cudaq.translate`
# we round the exponent of each gate
for op in cirq_qc.all_operations():
if hasattr(op.gate, "exponent"):
op.gate._exponent = np.round(op.gate.exponent, 6) # type: ignore

return cirq_qc


def to_cudaq(circuit: Circuit) -> PyKernel:
"""Convert a cirq Circuit to a cudaq PyKernel via OpenQASM 3.

Args:
circuit (Circuit): The cirq Circuit to convert.

Returns:
PyKernel: The converted cudaq PyKernel.
"""
qiskit_qc = to_qiskit(circuit)

qiskit_qc = transpile(
qiskit_qc,
basis_gates=["x", "y", "z", "h", "rx", "rz", "cx", "ccx"],
optimization_level=0,
)

cudaq_qc = cudaq.make_kernel()
qr = cudaq_qc.qalloc(qiskit_qc.num_qubits)

for gate in qiskit_qc:
if gate.name == "x":
qubit = gate.qubits[0]._index
cudaq_qc.x(qr[qubit])
elif gate.name == "y":
qubit = gate.qubits[0]._index
cudaq_qc.y(qr[qubit])
elif gate.name == "z":
qubit = gate.qubits[0]._index
cudaq_qc.z(qr[qubit])
elif gate.name == "h":
qubit = gate.qubits[0]._index
cudaq_qc.h(qr[qubit])
elif gate.name == "rx":
qubit = gate.qubits[0]._index
param = gate.params[0]
cudaq_qc.rx(param, qr[qubit])
elif gate.name == "rz":
qubit = gate.qubits[0]._index
param = gate.params[0]
cudaq_qc.rz(param, qr[qubit])
elif gate.name == "cx":
qubits = [qubit._index for qubit in gate.qubits]
cudaq_qc.cx(qr[qubits[0]], qr[qubits[1]])
elif gate.name in ["ccx", "toffoli"]:
qubits = [qubit._index for qubit in gate.qubits]
cudaq_qc.cx([qr[qubits[0]], qr[qubits[1]]], qr[qubits[2]])
elif gate.name == "measure":
qubits = [qubit._index for qubit in gate.qubits]
for qubit in qubits:
cudaq_qc.mz(qr[qubit])

return cudaq_qc
14 changes: 14 additions & 0 deletions mitiq/interface/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,17 @@ def _count_gate_arities_qibo(circuit: Any) -> dict[str, int]:
return counts


# TODO: Temporary solution until we can go through the
# operations without using `cudaq.translate(qc, format="openqasm2")`
# and doing so via that framework
def _count_gate_arities_cudaq(circuit: Any) -> dict[str, int]:
"""Counts gates in a Cudaq circuit grouped by arity."""
from mitiq.interface.conversions import convert_to_mitiq

circuit = convert_to_mitiq(circuit)[0]
return _count_gate_arities_cirq(circuit)


def _get_circuit_type(circuit: QPROGRAM) -> str:
"""Returns the framework type of ``circuit``."""
try:
Expand All @@ -156,6 +167,8 @@ def _get_circuit_type(circuit: QPROGRAM) -> str:
return "pennylane"
if "qibo" in package:
return "qibo"
if "cudaq" in package:
return "cudaq"
if isinstance(circuit, cirq.Circuit):
return "cirq"
raise UnsupportedCircuitError(
Expand All @@ -170,6 +183,7 @@ def _get_circuit_type(circuit: QPROGRAM) -> str:
"braket": _count_gate_arities_braket,
"pennylane": _count_gate_arities_pennylane,
"qibo": _count_gate_arities_qibo,
"cudaq": _count_gate_arities_cudaq,
}


Expand Down
2 changes: 1 addition & 1 deletion mitiq/pec/tests/test_pec.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ def test_execute_with_pec_mitigates_noise(circuit, executor, circuit_type):
true_noiseless_value = 1.0
unmitigated = serial_executor(circuit)

if circuit_type in ["qiskit", "pennylane", "qibo"]:
if circuit_type in ["qiskit", "pennylane", "qibo", "cudaq"]:
# Note this is an important subtlety necessary because of conversions.
reps = get_pauli_and_cnot_representations(
base_noise=BASE_NOISE,
Expand Down
2 changes: 2 additions & 0 deletions mitiq/tests/test_conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from braket.circuits import Circuit as BKCircuit
from braket.circuits import Instruction
from braket.circuits import gates as braket_gates
from cudaq import PyKernel
from pyquil import Program, gates

from mitiq import SUPPORTED_PROGRAM_TYPES
Expand Down Expand Up @@ -63,6 +64,7 @@
"braket": BKCircuit,
"pennylane": qml.tape.QuantumTape,
"qibo": qibo.models.circuit.Circuit,
"cudaq": PyKernel,
}


Expand Down
13 changes: 12 additions & 1 deletion mitiq/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,20 @@ def keys(cls) -> list[str]:
except ImportError: # pragma: no cover
_QiboCircuit = _Circuit

try:
from cudaq import PyKernel as _CudaqCircuit
except ImportError: # pragma: no cover
_CudaqCircuit = _Circuit

# Supported + installed quantum programs.
QPROGRAM = Union[
_Circuit, _Program, _QuantumCircuit, _BKCircuit, _QuantumTape, _QiboCircuit
_Circuit,
_Program,
_QuantumCircuit,
_BKCircuit,
_QuantumTape,
_QiboCircuit,
_CudaqCircuit,
]


Expand All @@ -80,6 +90,7 @@ class SUPPORTED_PROGRAM_TYPES(EnhancedEnum):
PYQUIL = _Program
QIBO = _QiboCircuit
QISKIT = _QuantumCircuit
CUDAQ = _CudaqCircuit


# Define MeasurementResult, a result obtained by measuring qubits on a quantum
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ qiskit = [
"qiskit-ibm-runtime~=0.37.0",
"ply==3.11",
]
cudaq = [
"cudaq~=0.12.0"
]

[dependency-groups]
dev = [
Expand Down
14 changes: 12 additions & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading