PennyLaneAI / pennylane

PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.
https://pennylane.ai
Apache License 2.0
2.27k stars 585 forks source link

[BUG] `default.qubit.autograd` returns a tensor with `requires_grad=True` #1726

Open antalszava opened 2 years ago

antalszava commented 2 years ago

Expected behavior

The output of circuit execution is a tensor that has the requires_grad attribute set to True or False for all devices.

Most devices output a tensor with requires_grad=False:

import pennylane as qml
from pennylane.devices import DefaultQubit

dev = qml.device('lightning.qubit', wires=1)

@qml.qnode(dev)
def circuit():
    qml.PauliX(0)
    return qml.expval(qml.PauliX(0))

circuit()
tensor(0., requires_grad=False)

Actual behavior

The default.qubit.autograd device outputs a tensor with requires_grad=False:

import pennylane as qml
from pennylane.devices import DefaultQubit

dev = qml.device('default.qubit.autograd', wires=1)

@qml.qnode(dev)
def circuit():
    qml.PauliX(0)
    return qml.expval(qml.PauliX(0))

circuit()
tensor(0., requires_grad=True)

With a tape:

import pennylane as qml
from pennylane.devices import DefaultQubit

device = qml.device('default.qubit.autograd', wires=1)

with qml.tape.QuantumTape() as tape:
    qml.PauliX(0)
    qml.expval(qml.PauliX(0))

res = device.execute(tape)

print(res, type(res), res.requires_grad)
[0.] <class 'pennylane.numpy.tensor.tensor'> True

Having requires_grad=True for the output seems to be unified for all devices when using the beta QNode:

import pennylane as qml
from pennylane.devices import DefaultQubit

dev = qml.device('lightning.qubit', wires=1)

@qml.beta.qnode(dev)
def circuit():
    qml.PauliX(0)
    return qml.expval(qml.PauliX(0))

circuit()
tensor(0., requires_grad=True)

Additional information

The issue to tackle would be to have a standardized output. Switching to the beta QNode seems to solve this, however, I'm not sure if having requires_grad=True for all outputs was intentional behaviour. If so, then this issue can be simply closed. :slightly_smiling_face:

Source code

No response

Tracebacks

No response

System information

Name: PennyLane
Version: 0.19.0.dev0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/XanaduAI/pennylane
Author: None
Author-email: None
License: Apache License 2.0
Location: /pennylane
Requires: numpy, scipy, networkx, autograd, toml, appdirs, semantic-version, autoray, cachetools, pennylane-lightning
Required-by: PennyLane-Cirq, PennyLane-Orquestra, PennyLane-SF, pennylane-qulacs, PennyLane-IonQ, amazon-braket-pennylane-plugin, PennyLane-Forest, PennyLane-Honeywell, PennyLane-qiskit, PennyLane-AQT, PennyLane-Lightning, PennyLane-Qchem
Platform info:           Linux-5.11.0-37-generic-x86_64-with-glibc2.10
Python version:          3.8.5
Numpy version:           1.20.3
Scipy version:           1.7.1
Installed devices:
- cirq.mixedsimulator (PennyLane-Cirq-0.17.1)
- cirq.pasqal (PennyLane-Cirq-0.17.1)
- cirq.qsim (PennyLane-Cirq-0.17.1)
- cirq.qsimh (PennyLane-Cirq-0.17.1)
- cirq.simulator (PennyLane-Cirq-0.17.1)
- orquestra.forest (PennyLane-Orquestra-0.15.0)
- orquestra.ibmq (PennyLane-Orquestra-0.15.0)
- orquestra.qiskit (PennyLane-Orquestra-0.15.0)
- orquestra.qulacs (PennyLane-Orquestra-0.15.0)
- strawberryfields.fock (PennyLane-SF-0.16.0.dev0)
- strawberryfields.gaussian (PennyLane-SF-0.16.0.dev0)
- strawberryfields.gbs (PennyLane-SF-0.16.0.dev0)
- strawberryfields.remote (PennyLane-SF-0.16.0.dev0)
- strawberryfields.tf (PennyLane-SF-0.16.0.dev0)
- qulacs.simulator (pennylane-qulacs-0.17.0.dev0)
- ionq.qpu (PennyLane-IonQ-0.17.0.dev0)
- ionq.simulator (PennyLane-IonQ-0.17.0.dev0)
- braket.aws.qubit (amazon-braket-pennylane-plugin-1.4.1.dev0)
- braket.local.qubit (amazon-braket-pennylane-plugin-1.4.1.dev0)
- forest.numpy_wavefunction (PennyLane-Forest-0.17.0.dev0)
- forest.qvm (PennyLane-Forest-0.17.0.dev0)
- forest.wavefunction (PennyLane-Forest-0.17.0.dev0)
- honeywell.hqs (PennyLane-Honeywell-0.16.0.dev0)
- qiskit.aer (PennyLane-qiskit-0.18.0.dev0)
- qiskit.basicaer (PennyLane-qiskit-0.18.0.dev0)
- qiskit.ibmq (PennyLane-qiskit-0.18.0.dev0)
- aqt.noisy_sim (PennyLane-AQT-0.18.0)
- aqt.sim (PennyLane-AQT-0.18.0)
- lightning.qubit (PennyLane-Lightning-0.19.0.dev0)
- default.gaussian (PennyLane-0.19.0.dev0)
- default.mixed (PennyLane-0.19.0.dev0)
- default.qubit (PennyLane-0.19.0.dev0)
- default.qubit.autograd (PennyLane-0.19.0.dev0)
- default.qubit.jax (PennyLane-0.19.0.dev0)
- default.qubit.tf (PennyLane-0.19.0.dev0)
- default.qubit.torch (PennyLane-0.19.0.dev0)
- default.tensor (PennyLane-0.19.0.dev0)
- default.tensor.tf (PennyLane-0.19.0.dev0)


### 

- [X] I have searched exisisting GitHub issues to make sure the issue does not already exist.
josh146 commented 2 years ago

Hey @antalszava to answer your question --- the output differentiability should ideally depend on two things:

If a QNode has differentiable input, and the QNode has a gradient method, the output should have requires_grad=True 🙂

However, if all input to a QNode is requires_grad=False, the output should always be requires_grad=False. Similarly, if the QNode is not differentiable (diff_method=None), the output should also be non-differentiable.

You can compare to standard NumPy functions:

>>> x = np.array(0.1, requires_grad=True)
>>> y = np.array(0.2, requires_grad=True)
>>> np.add(x, y)  # both inputs require grad
tensor(0.3, requires_grad=True)
>>> x.requires_grad = False
>>> np.add(x, y) # one input requires grad
tensor(0.3, requires_grad=True)
>>> y.requires_grad = False
>>> np.add(x, y) # no inputs require grad
tensor(0.3, requires_grad=False)
antalszava commented 2 years ago

Oh, that's a great cue to keep in mind, thanks @josh146! :slightly_smiling_face:

I think we have issues for the following cases then:

However, if all input to a QNode is requires_grad=False, the output should always be requires_grad=False.

A QNode with no trainable parameters will have an output with requires_grad=True when the beta QNode is in place:

import pennylane as qml
from pennylane.devices import DefaultQubit

dev = qml.device('lightning.qubit', wires=1)

@qml.beta.qnode(dev)
def circuit():
    return qml.expval(qml.PauliZ(0))

assert circuit().requires_grad

For now, this issue comes up only with default.qubit.autograd:

import pennylane as qml
from pennylane.devices import DefaultQubit

dev = qml.device('default.qubit.autograd', wires=1)

@qml.qnode(dev)
def circuit():
    return qml.expval(qml.PauliZ(0))

assert circuit().requires_grad

Not with other devices:

import pennylane as qml
from pennylane.devices import DefaultQubit

dev = qml.device('lightning.qubit', wires=1)

@qml.qnode(dev)
def circuit():
    return qml.expval(qml.PauliZ(0))

assert not circuit().requires_grad
josh146 commented 2 years ago

For now, this issue comes up only with default.qubit.autograd:

Good catch. This must be because of something to do with backpropagation 🤔

josh146 commented 2 years ago

One more question @antalszava, is this bug causing another bug? Just curious about priority in fixing this.

antalszava commented 2 years ago

No, it was just something that we've stumbled upon with Catalina when playing around with PennyLane. Likely it's a matter of standardizing this behaviour.

At the moment we have it on the radar, but not considering it as a high priority bug.

josh146 commented 2 years ago

Good to know 😅 yep if it is not blocking anything or causing any other bugs, not too high priority for now

Jaybsoni commented 2 years ago

Just to confirm that I understand the problem, it seems that the requires_grad attribute of the tensor returned by these Qnodes seems to be inconsistent.

Ideally: The output of such a qnode should only be differentiable (i.e requires_grad=True) if: any(input.requires_grad for input in inputs) and Qnode.diff_method is not None ?

Currently:

We want to change that behaviour to match the ideal case. Is that correct?

josh146 commented 2 years ago

Yep! You're right to separate this into two separate use-cases, since backprop mode and parameter-shift/adjoint mode go through two very different logical pathways.

Note: the reason it works for the old QNode in parameter-shift mode is that we are explicitly taking this into account: https://github.com/PennyLaneAI/pennylane/blob/c81a0389257a73696d90aa108d930e6c0d607a17/pennylane/interfaces/autograd.py#L174

timmysilv commented 1 year ago

I get the feeling this issue doesn't really apply anymore, but if it does, it might be related to #4350 ?