forked from NVIDIA/cuda-quantum
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathqiskit_convert.py
More file actions
201 lines (166 loc) · 7.36 KB
/
qiskit_convert.py
File metadata and controls
201 lines (166 loc) · 7.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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