Gate adjoint not cancelling gate

I created a very specific circuit, and something weird is happening : if I add some doubly controlled H gates, and then their adjoint operation, I don’t get the initial state. I first thought it could be a bug, but as I’m not very experienced in quantum computing I wanted to check I didn’t do a mistake.

More concretely, here is a portion of code showing the problem:

dev = qml.device("default.qubit", wires=10, shots=5)

def CHadamard(wires):
    matrix = qml.matrix(qml.Hadamard)(wires=0)
    qml.ControlledQubitUnitary(matrix, control_wires=wires[0], wires=[wires[1]])

def CCHadamard(wires):
    matrix = qml.matrix(CHadamard)(wires=range(2))
    qml.ControlledQubitUnitary(matrix, control_wires=wires[0], wires=[wires[1],wires[2]])

@qml.qnode(dev)
def test():
    qml.Hadamard(0)
    qml.Hadamard(1)
    qml.Hadamard(2)
    qml.Hadamard(3)
    qml.Hadamard(4)
    qml.Hadamard(5)
    qml.Hadamard(6)

    CCHadamard(wires=(0,1,9))
    CCHadamard(wires=(0,1,8))
    qml.adjoint(CCHadamard)(wires=(0,1,8))
    qml.adjoint(CCHadamard)(wires=(0,1,9))

    return qml.state()

res = test()
for state in res:
if state.real < 0:
    print(state)

This shows 64 states with a negative real part. However, this should display 0 state, because the CCH gates and their adjoints are supposed to cancel each other.
And if I run the same code without the CCH part, I indeed get no negative state.

What’s going on, did I miss something or is it a bug ? (if that’s the case I can of course create a github issue)

Thanks for your help.

Hi @Asimonu,

I think the main issue in your code is that you’re not adding Hadamards at the end. The best way of finding if the adjoint is working is by making sure you go back to the zero state. I have made a more simplified version of your code. I’ve tried to add a lot of comments to make it easier to understand but please let me know if you’re confused by any of the items here. Note that here I use less qubits and only one CCH plus its adjoint, but you can easily modify this to make it do exactly what you need.

import pennylane as qml

n_wires = 5 # Number of qubits
control1 = 0 # First control qubit
control2 = 1 # Second control qubit
target = 4 # Target qubit to apply the controlled operation to

dev = qml.device("default.qubit", wires=n_wires) # create the device

ccHadamard = qml.ctrl(lambda wire:qml.Hadamard(wires=wire), control=[control1, control2]) # Create double-controlled operation
    
@qml.qnode(dev)
def small_test():
    # we start with a zero state
    # add Hadamards to the first n-1 wires
    qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=range(n_wires-1))
    # apply the double controlled hadamard to the circuit
    ccHadamard(wire=target)
    # apply adjoint of the double controlled hadamard to the circuit
    qml.adjoint(ccHadamard)(wire=target)
    # add Hadamards to the first n-1 wires to return to the initial state (zero)
    qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=range(n_wires-1))
    return qml.state()

fig, ax = qml.draw_mpl(small_test, expansion_strategy='device', show_all_wires='true')()
fig.show()

If you need to make the controlled version of more complex gates it may be worth creating a function for these sets of gates. You can learn more about qml.ctrl here.

For the drawing you can remove the expansion_strategy='device' in case you want to show the block instead of the decomposition.

Please let me know if this helps!

1 Like

Thanks for the good practice tips, in particular for the good way to do a multi controlled gate.
The problem with the negative real part still occurs, but I realized it is always equal to something very small, so it is likely that float imprecision is responsible of it.

Hi @Asimonu,

I’m glad this helped!

Yes, the float imprecision can be the one responsible for this.

One way of not being confused by this is by just looking at the first element of the output state. If it’s 1 or close to 1 it means we have the zero state.

res = small_test()

print('res: ',res) # if the first element is 1 it means that we've got the zero state (which is what we want)