QNode returns

Version 0.30.0 of PennyLane updated the return type of a QNode. This page provides additional details about the update and can be used to troubleshoot issues.

Note

If you are looking for a quick fix, jump to the Troubleshooting section!

After visiting the Troubleshooting section, if you are still stuck then you can:

  • Post on the PennyLane discussion forum.

  • If you suspect that your issue is due to a bug in PennyLane itself, please open a bug report on the PennyLane GitHub page.

Note

The old return system has been completely removed from PennyLane as of version 0.33.0, along with the enable_return() and disable_return() toggle functions.

Summary of the update

../_images/new_return_types.png

PennyLane QNodes now return exactly what you tell them to! 🎉

Consider the following circuit:

import pennylane as qml

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

@qml.qnode(dev)
def circuit(x):
    qml.RX(x, wires=0)
    return qml.expval(qml.PauliZ(0)), qml.probs(0)

In version 0.29 and earlier of PennyLane, circuit() would return a single length-3 array:

>>> circuit(0.5)
tensor([0.87758256, 0.93879128, 0.06120872], requires_grad=True)

In versions 0.30 and above, circuit() returns a length-2 tuple containing the expectation value and probabilities separately:

>>> circuit(0.5)
(tensor(0.87758256, requires_grad=True),
 tensor([0.93879128, 0.06120872], requires_grad=True))

Motivation

PennyLane has historically adopted the approach of combining the returned measurements of a QNode into a single array. However, this has presented some challenges:

  • The return of a QNode could be different from what is expected, as shown in the example above.

  • For measurements of different shapes, ragged arrays were generated internally and then squeezed into a single output array. This is incompatible with NumPy’s NEP 34 and constrains the version of NumPy that PennyLane is compatible with.

  • Use of stacking and squeezing presents performance bottlenecks.

The changes made in version 0.30 of PennyLane address the challenges above. However, existing users may experience breaking changes or issues when upgrading their PennyLane version.

Troubleshooting

You may experience issues with PennyLane’s updated QNode return system in version 0.30 and above if you have existing code that works with an earlier version of PennyLane. To help identify a fix, select the option below that describes your situation.

Your issue may be because:

  • You are calculating the Jacobian of the QNode using the NumPy or TensorFlow interface. For example, the following will now raise an error:

    from pennylane import numpy as np
    
    dev = qml.device("default.qubit", wires=1)
    
    @qml.qnode(dev)
    def circuit(x):
        qml.RX(x, wires=0)
        return qml.expval(qml.PauliY(0)), qml.expval(qml.PauliZ(0))
    
    x = np.array(0.5, requires_grad=True)
    qml.jacobian(circuit)(x)
    

    Use stacking to fix this issue (see below), which arises because NumPy and TensorFlow do not support differentiating tuples. Alternatively, consider porting your code to use the JAX or Torch interface, which could unlock additional features and performance benefits!

with tf.GradientTape() as tape:
    res = circuit(a, b)
    res = tf.stack(res)

x = np.array(0.5, requires_grad=True)
qml.jacobian(circuit)(x)
  • You are returning differently-shaped quantities together, such as expval() and probs(). For example, the following code is compatible with version 0.29 of PennyLane but will raise an error in version 0.30 and above:

    dev = qml.device("default.qubit", wires=1)
    
    @qml.qnode(dev)
    def circuit(x):
        qml.RX(x, wires=0)
        return qml.expval(qml.PauliZ(0)), qml.probs(0)
    
    def result(x):
        expval, p0, p1 = circuit(x)
        return expval + p0 - p1
    
    x = np.array(0.5, requires_grad=True)
    result(x)
    

    Such issues can be addressed by updating how the return of a QNode is processed, being aware of unpacking, slicing, and indexing. The example above would be fixed simply by updating result() to:

    def result(x):
        expval, (p0, p1) = circuit(x)
        return expval + p0 - p1
    

If you are a device developer, your issue may be because:

In either case, please reach out to us for guidance!

Please carefully read through the options above. If you are still stuck, you can:

  • Post on the PennyLane discussion forum. Please include a complete block of code demonstrating your issue so that we can quickly troubleshoot.

  • If you suspect that your issue is due to a bug in PennyLane itself, please open a bug report on the PennyLane GitHub page.