NVIDIA / cuda-quantum

C++ and Python support for the CUDA Quantum programming model for heterogeneous quantum-classical workflows
https://nvidia.github.io/cuda-quantum/
Other
501 stars 182 forks source link

Unexpected behavior when kernel takes list of booleans #2262

Open jezerjojo14 opened 2 weeks ago

jezerjojo14 commented 2 weeks ago

Required prerequisites

Describe the bug

I needed to send a list of $n$ booleans as a parameter to a kernel to specify which of $n$ qubits to apply $X$ gates on. The kernel does not throw any errors, but does not seem to be interpreting this list of booleans correctly. I'm not sure but I think it might be interpreting the list of booleans as a single integer?

Steps to reproduce the bug

This is the kernel:

import cudaq
@cudaq.kernel
def circ_test(n: int, bools: list[bool]):
    q = cudaq.qvector(n)
    for j in range(n):
        b=bools[j]
        if b==False:
            x(q[j])

This is how it behaves for different examples:

1)

print(cudaq.draw(circ_test,5,[True,True,True,True,True]))

q0 : ─────
     ╭───╮
q1 : ┤ x ├
     ├───┤
q2 : ┤ x ├
     ├───┤
q3 : ┤ x ├
     ├───┤
q4 : ┤ x ├
     ╰───╯

2)

print(cudaq.draw(circ_test,5,[False,True,True,False,True]))

q0 : ─────
     ╭───╮
q1 : ┤ x ├
     ├───┤
q2 : ┤ x ├
     ├───┤
q3 : ┤ x ├
     ├───┤
q4 : ┤ x ├
     ╰───╯

3)

print(cudaq.draw(circ_test,5,[False,False,False,False,False]))

     ╭───╮
q0 : ┤ x ├
     ├───┤
q1 : ┤ x ├
     ├───┤
q2 : ┤ x ├
     ├───┤
q3 : ┤ x ├
     ├───┤
q4 : ┤ x ├
     ╰───╯

Here's a modified kernel:

import cudaq
@cudaq.kernel
def circ_test_2(n: int, bools: list[bool]):
    q = cudaq.qvector(n)
    for j in range(n):
        b=bools[j]
        if b==True:
            x(q[j])

1)

print(cudaq.draw(circ_test_2,5,[False,False,False,False,False]))

No output

2)

print(cudaq.draw(circ_test_2,5,[True,True,True,True,True]))

No output

3)

print(cudaq.draw(circ_test_2,5,[False,True,False,False,True]))

No output

4)

print(cudaq.draw(circ_test_2,5,[True,False,False,False,False]))

     ╭───╮
q0 : ┤ x ├
     ╰───╯

Expected behavior

For the first kernel, I expect an $X$ gate to be applied on the $i$-th qubit if and only if the $i$-th element of the boolean list is False. For the second kernel I expect the same but for when the $i$-th element of the boolean list is True.

Is this a regression? If it is, put the last known working version (or commit) here.

Not a regression

Environment

Suggestions

No response

bebora commented 1 week ago

Hi @jezerjojo14 , I tested some other variants of your code, changing the value being checked, the defined type for the list and the actual values in the list.

Boolean list, boolean comparison

import cudaq
@cudaq.kernel
def circ_test(n: int, bools: list[bool]):
    q = cudaq.qvector(n)
    for j in range(n):
        b=bools[j]
        if b==True:
            x(q[j])

print(cudaq.draw(circ_test,5,[False,True,False,True,False]))

Output: Empty If we were to call the kernel with a list of integers, it would throw an error:

print(cudaq.draw(circ_test,5,[0,1,0,1,0]))

Output: RuntimeError: kernel argument's element type is 'bool' but argument provided is not (argument 1, element 0, value=0, type=<class 'int'>).

Boolean list, integer comparison

import cudaq
@cudaq.kernel
def circ_test(n: int, bools: list[bool]):
    q = cudaq.qvector(n)
    for j in range(n):
        b=bools[j]
        if b==1:
            x(q[j])

print(cudaq.draw(circ_test,5,[False,True,False,True,False]))

Output: Empty

Integer list, boolean comparison

import cudaq
@cudaq.kernel
def circ_test(n: int, bools: list[int]):
    q = cudaq.qvector(n)
    for j in range(n):
        b=bools[j]
        if b==True:
            x(q[j])

print(cudaq.draw(circ_test,5,[0,1,0,1,0]))

Output:

error: 'arith.cmpi' op requires all operands to have the same type
RuntimeError: Failure while executing pass pipeline.

Integer list, integer comparison

import cudaq
@cudaq.kernel
def circ_test(n: int, bools: list[int]):
    q = cudaq.qvector(n)
    for j in range(n):
        b=bools[j]
        if b==1:
            x(q[j])

print(cudaq.draw(circ_test,5,[0,1,0,1,0]))

Output:

q0 : ─────
     ╭───╮
q1 : ┤ x ├
     ╰───╯
q2 : ─────
     ╭───╮
q3 : ┤ x ├
     ╰───╯

If we were to call the kernel with a list of booleans, it would work too:

print(cudaq.draw(circ_test,5,[False,True,False,True,False]))

Output:

q0 : ─────
     ╭───╮
q1 : ┤ x ├
     ╰───╯
q2 : ─────
     ╭───╮
q3 : ┤ x ├
     ╰───╯

Takeaway

You can currently call your kernel passing a list of booleans as long as the kernel treats it as a list of integers. There is likely something wrong when using lists of booleans inside quantum kernels. A simplified version of your code where the kernel receives a bool instead of a list[bool] works fine, so the bool type is not problematic per se.

jezerjojo14 commented 1 week ago

I'm curious as to what was going wrong in the first place but this is a great solution to the problem I was facing. Thanks @bebora 👍