Measuring Kernels¶
import cudaq
#include <cudaq.h>
Kernel measurement can be specified in the Z, X, or Y basis using mz, mx, and my. Measurement occurs in the Z basis by default.
@cudaq.kernel
def kernel():
qubits = cudaq.qvector(2)
mz(qubits)
__qpu__ void kernel0() {
cudaq::qvector qubits(2);
mz(qubits[0]);
}
Specific qubits or registers can be measured rather than the entire kernel.
@cudaq.kernel
def kernel():
qubits_a = cudaq.qvector(2)
qubit_b = cudaq.qubit()
mz(qubits_a)
mx(qubit_b)
__qpu__ void kernel1() {
cudaq::qvector qubits_a(2);
cudaq::qubit qubits_b;
mz(qubits_a);
mx(qubits_b);
}
Measurement Handles¶
In CUDA-Q, mz, mx, and my return a measurement
handle — cudaq::measure_handle in C++ (with the alias
cudaq::measure_result), and the measure_handle type in
Python — rather than a classical value. Measuring a single qubit returns one
handle; measuring a qvector returns a vector of handles. A handle
records a measurement event and defers reading its classical value, so the
same measurement can drive mid-circuit conditional logic and
quantum-error-correction declarations (see Detector Error Models).
A handle is discriminated into its classical bit by using it in a boolean
context inside the kernel — for example the if (b0) test in the
mid-circuit example below. To read a whole vector of handles at once,
discriminate it in bulk with to_bools (yielding a
list[bool] / std::vector<bool>) or to_integer
(packing the bits little-endian into an integer). The C++ mid-circuit example
below returns to_bools(mz(q)); a Python kernel typed to return
list[bool] discriminates a returned handle vector automatically.
A handle cannot cross the host-device boundary without being discriminated:
convert it to a boolean, list[bool] / std::vector<bool>, or
integer inside the kernel before returning it.
Mid-circuit Measurement and Conditional Logic¶
In certain cases, it is helpful for some operations in a quantum kernel to depend on measurement results following previous operations. This is accomplished in the following example by performing a Hadamard on qubit 0, then measuring qubit 0 and saving the result as b0. Then, qubit 0 can be reset and used later in the computation. In this case it is flipped to a 1. Finally, an if statement performs a Hadamard on qubit 1 if b0 is 1.
The results show qubit 0 is one, indicating the reset worked, and qubit 1 has a 75/25 distribution, demonstrating the mid-circuit measurement worked as expected.
@cudaq.kernel
def kernel() -> list[bool]:
q = cudaq.qvector(2)
h(q[0])
b0 = mz(q[0])
reset(q[0])
x(q[0])
if b0:
h(q[1])
return mz(q)
from collections import Counter
results = cudaq.run(kernel, shots_count=1000)
# Convert results to bitstrings and count
bitstring_counts = Counter(
''.join('1' if bit else '0' for bit in result) for result in results)
print(f"Bitstring counts: {dict(bitstring_counts)}")
__qpu__ std::vector<bool> kernel2() {
cudaq::qvector q(2);
h(q[0]);
auto b0 = mz(q[0]);
cudaq::reset(q[0]);
x(q[0]);
if (b0) {
h(q[1]);
}
return cudaq::to_bools(mz(q));
}
int main() {
auto results = cudaq::run(1000, kernel2);
// Count occurrences of each bitstring
std::map<std::string, std::size_t> bitstring_counts;
for (const auto &result : results) {
std::string bits = std::to_string(static_cast<int>(result[0])) +
std::to_string(static_cast<int>(result[1]));
bitstring_counts[bits]++;
}
printf("Bitstring counts:\n{\n");
for (const auto &[bits, count] : bitstring_counts) {
printf(" %s: %zu\n", bits.c_str(), count);
}
printf("}\n");
return 0;
}
Output
Bitstring counts: {'11': 247, '10': 753}
Bitstring counts:
{
10: 771
11: 229
}