Skip to content
Merged
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
11 changes: 10 additions & 1 deletion .github/workflows/test_in_devenv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@ jobs:
fi
python -m pip install pytest
python -m pytest -v --durations=0 python/tests/ \
--ignore python/tests/backends
--ignore python/tests/backends \
--ignore python/tests/contrib
pytest_status=$?
if [ ! $pytest_status -eq 0 ]; then
echo "::error file=test_in_devenv.yml::Python tests failed with status $pytest_status."
Expand All @@ -270,6 +271,14 @@ jobs:
exit 1
fi

pip install qiskit
python -m pytest -v --durations=0 python/tests/contrib
pytest_status=$?
if [ ! $pytest_status -eq 0 ]; then
echo "::error file=test_in_devenv.yml::Python contrib tests failed with status $pytest_status."
exit 1
fi

- name: Save environment
id: env_save
if: inputs.export_environment
Expand Down
9 changes: 9 additions & 0 deletions python/cudaq/contrib/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# ============================================================================ #
# Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. #
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Copyright for new files ought to be simply 2026, I believe.

# All rights reserved. #
# #
# This source code and the accompanying materials are made available under #
# the terms of the Apache License 2.0 which accompanies this distribution. #
# ============================================================================ #

from .qiskit_convert import from_qiskit, from_qasm
201 changes: 201 additions & 0 deletions python/cudaq/contrib/qiskit_convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# ============================================================================ #
# Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. #
# All rights reserved. #
# #
# This source code and the accompanying materials are made available under #
# the terms of the Apache License 2.0 which accompanies this distribution. #
# ============================================================================ #
"""Functions to convert Qiskit circuits and OpenQASM files to CUDA-Q kernels.

This module provides interoperability between Qiskit and CUDA-Q,
allowing users to convert Qiskit `QuantumCircuit` objects and OpenQASM
files into CUDA-Q kernels for simulation and execution.

Note:
This module requires ``qiskit`` to be installed.
"""

import numpy as np

from ..kernel.kernel_builder import make_kernel


def _try_import_qiskit():
"""Import Qiskit `QuantumCircuit`.

Returns:
The `QuantumCircuit` class from Qiskit.

Raises:
ImportError: If Qiskit is not installed.
"""
try:
from qiskit import QuantumCircuit
except ImportError as e:
raise ImportError("This feature requires Qiskit. "
"Install it with: `pip install qiskit`") from e
return QuantumCircuit


def from_qasm(qasm_file):
"""Create a CUDA-Q kernel from an OpenQASM file.

This function reads an OpenQASM file and converts it to a CUDA-Q kernel
by first parsing it with Qiskit and then converting the resulting circuit.

Args:
`qasm_file`: Path to the OpenQASM file as a string.

Returns:
A CUDA-Q kernel equivalent to the OpenQASM circuit.

Raises:
ImportError: If Qiskit is not installed.
FileNotFoundError: If the QASM file does not exist.
RuntimeError: If the QASM file cannot be parsed.
"""
QuantumCircuit = _try_import_qiskit()
try:
qiskit_circuit = QuantumCircuit.from_qasm_file(qasm_file)
except FileNotFoundError:
raise
except Exception as e:
raise RuntimeError(
f"Could not parse QASM file '{qasm_file}': {e}") from e

return from_qiskit(qiskit_circuit)


def from_qiskit(qiskit_circuit):
"""Create a CUDA-Q kernel from a Qiskit `QuantumCircuit`.

This function converts a Qiskit `QuantumCircuit` to an equivalent CUDA-Q
kernel by mapping Qiskit gates to their CUDA-Q counterparts.

Args:
`qiskit_circuit`: A `Qiskit.QuantumCircuit` instance.

Returns:
A CUDA-Q kernel equivalent to the input Qiskit circuit.

Raises:
ImportError: If Qiskit is not installed.
ValueError: If the circuit contains unsupported gates.

Supported gates:
- Single qubit: `h`, `x`, `y`, `z`, `s`, `t`, `sdg`, `tdg`, `id` (identity)
- Two qubit: `cx`, `cy`, `cz`, `ch`, `swap`, `rxx`, `rzz`
- Three qubit: `ccx` (Toffoli)
- Parametric: `rx`, `ry`, `rz`, `r1`, `u3`, `u`, `p` (phase)
- Controlled parametric: `crx`, `cry`, `crz`
- Special: `sx`, `sxdg`, barrier, measure
"""
# Ensure Qiskit is available (validates input type indirectly)
_try_import_qiskit()

kernel = make_kernel()
num_qubits = qiskit_circuit.num_qubits
qubits = kernel.qalloc(num_qubits)

for instruction in qiskit_circuit.data:
op_name = instruction.operation.name
params = [float(p) for p in instruction.operation.params]
q_indices = [
qiskit_circuit.find_bit(q).index for q in instruction.qubits
]

# Single qubit gates
if op_name == 'h':
kernel.h(qubits[q_indices[0]])
elif op_name == 'x':
kernel.x(qubits[q_indices[0]])
elif op_name == 'y':
kernel.y(qubits[q_indices[0]])
elif op_name == 'z':
kernel.z(qubits[q_indices[0]])
elif op_name == 's':
kernel.s(qubits[q_indices[0]])
elif op_name == 't':
kernel.t(qubits[q_indices[0]])
elif op_name == 'sdg':
kernel.sdg(qubits[q_indices[0]])
elif op_name == 'tdg':
kernel.tdg(qubits[q_indices[0]])
elif op_name == 'id':
pass # Identity gate, no operation needed

# Two qubit gates
elif op_name == 'cx':
kernel.cx(qubits[q_indices[0]], qubits[q_indices[1]])
elif op_name == 'cy':
kernel.cy(qubits[q_indices[0]], qubits[q_indices[1]])
elif op_name == 'cz':
kernel.cz(qubits[q_indices[0]], qubits[q_indices[1]])
elif op_name == 'ch':
kernel.ch(qubits[q_indices[0]], qubits[q_indices[1]])
elif op_name == 'swap':
kernel.swap(qubits[q_indices[0]], qubits[q_indices[1]])
elif op_name == 'rxx':
kernel.cx(qubits[q_indices[0]], qubits[q_indices[1]])
kernel.rx(params[0], qubits[q_indices[0]])
kernel.cx(qubits[q_indices[0]], qubits[q_indices[1]])
elif op_name == 'rzz':
kernel.cx(qubits[q_indices[0]], qubits[q_indices[1]])
kernel.rz(params[0], qubits[q_indices[0]])
kernel.cx(qubits[q_indices[0]], qubits[q_indices[1]])

# Toffoli gate (3-qubit) - use `cx` with list of controls
elif op_name == 'ccx':
kernel.cx([qubits[q_indices[0]], qubits[q_indices[1]]],
qubits[q_indices[2]])

# Parametric single qubit rotations
elif op_name == 'rx':
kernel.rx(params[0], qubits[q_indices[0]])
elif op_name == 'ry':
kernel.ry(params[0], qubits[q_indices[0]])
elif op_name == 'rz':
kernel.rz(params[0], qubits[q_indices[0]])
elif op_name == 'r1' or op_name == 'p':
# r1 and p (phase) gates are equivalent
kernel.r1(params[0], qubits[q_indices[0]])

# Controlled parametric rotations
elif op_name == 'crx':
kernel.crx(params[0], qubits[q_indices[0]], qubits[q_indices[1]])
elif op_name == 'cry':
kernel.cry(params[0], qubits[q_indices[0]], qubits[q_indices[1]])
elif op_name == 'crz':
kernel.crz(params[0], qubits[q_indices[0]], qubits[q_indices[1]])

# U3 gate: `U3(theta, phi, lambda) = Rz(lambda) Ry(theta) Rz(phi)`
elif op_name == 'u3' or op_name == 'u':
kernel.u3(params[0], params[1], params[2], qubits[q_indices[0]])

# `sqrt-X` gate decomposition
elif op_name == 'sx':
kernel.r1(np.pi / 4, qubits[q_indices[0]])
kernel.x(qubits[q_indices[0]])
kernel.r1(np.pi / 4, qubits[q_indices[0]])
kernel.x(qubits[q_indices[0]])
kernel.rx(np.pi / 2, qubits[q_indices[0]])
elif op_name == 'sxdg':
kernel.r1(-np.pi / 4, qubits[q_indices[0]])
kernel.x(qubits[q_indices[0]])
kernel.r1(-np.pi / 4, qubits[q_indices[0]])
kernel.x(qubits[q_indices[0]])
kernel.rx(-np.pi / 2, qubits[q_indices[0]])

# Barrier - no operation in CUDA-Q
elif op_name == 'barrier':
pass

# Measurements
elif op_name == 'measure':
kernel.mz(qubits[q_indices[0]])

else:
raise ValueError(f"Gate '{op_name}' is not supported. "
f"Cannot convert Qiskit circuit to CUDA-Q kernel.")

return kernel
Loading
Loading