microsoft / qsharp

Azure Quantum Development Kit, including the Q# programming language, resource estimator, and Quantum Katas
https://microsoft.github.io/qsharp/
MIT License
459 stars 91 forks source link

Attempt to generate controlled/adjoint variant when it is not requested #1929

Open tcNickolas opened 1 month ago

tcNickolas commented 1 month ago

Describe the bug

I'm trying to pick a random gate from an array, and using DrawRandomInt as the index fails because it doesn't support controlled and thus adjoint cannot be generated.

To Reproduce

operation Test3() : Bool {
    let ind = DrawRandomInt(0, 2);
    let op = [Rx, Ry, Rz][ind](1., _);
    let op1 = [Rx, Ry, Rz][DrawRandomInt(0, 2)](1., _);
    true
}

op and op1 are initialized in the same way (except the variable ind), but the second one yields an error.

Expected behavior

Two initializations of op and op1 should behave the same, since they do the same thing.

Screenshots

image

System information

swernli commented 1 month ago

This is an interesting one... I think the type system is working against you here in a valid but obscure way. The behavior of partial application is to try and turn the call invocation expression into the body of a function that matches the type of the expression being called, minus partial parameters. In this case, the expression being called is [Rx, Ry, Rz][DrawRandomInt(0, 2)] which correctly has type (Double, Qubit) => Unit is Adj + Ctl. The partial expression for the call is (1., _) which removes the Double and leaves the resolved type as Qubit => Unit is Adj + Ctl. So now the logic that creates the implied lambda does so with that type and the body of the expression, effectively creating:

operation PartialApplication(q : Qubit) : Unit is Adj + Ctl {
    [Rx, Ry, Rz][DrawRandomInt(0, 2)](1., _)
}

And then this fails adjoint and controlled generation later because DrawRandomInt is neither adjointable or controllable. I don't think there is a good way to fix this (short of incorporating it into #504, which is bigger than a bug fix). In the meantime, a workaround that could allow the operation to get chosen at random during each call would be using lambda syntax directly, which infers functors:

let op = q => [Rx, Ry, Rz][DrawRandomInt(0, 2)][1., q];

That will produce an op that gets a new choice from Rx, Ry, or Rz each time, but is not itself adjointable or controllable.