Repository: PennyLaneAI/pennylane

Issue:
Error when creating qnode with backprop and finite-shots
Fixes Issue #1481 

Right now, you can create a qnode that uses finite shots and backpropagation.  This is fine on qnode execution on the forward pass, but will always fail and raise some sort of unhelpful error message of the backward pass/ gradient computation.

For example:

```python
dev = qml.device('default.qubit.autograd', shots=10, wires=1)

@qml.qnode(dev, interface='autograd', diff_method='backprop')
def circ(x):
    qml.RX(x, wires=0)
    return qml.expval(qml.PauliZ(0))

## works fine
circ(0.1)

# incomprehensible error
qml.grad(circ)(0.1)
```

For context, that error is:
```
ValueError: setting an array element with a sequence.
```

With this change, during QNode creation, a `UserWarning` is raise.  I decided to use a warning instead of a Error, as users may still want to calculate finite shot forward passes on an interface device.  This way, they at least have an explanation for the strange errors they will end up getting.


Discussion:
Hello. You may have forgotten to update the changelog!
Please edit [.github/CHANGELOG.md](/PennyLaneAI/pennylane/blob/master/.github/CHANGELOG.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
[ch8003]
# [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1588?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1588](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1588?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (5f3eee2) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/67a5daf5a902ddafb0aa44c5d7bc1f98b19a7829?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (67a5daf) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1588/graphs/tree.svg?width=650&height=150&src=pr&token=9S3YzlUgGk&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1588?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

```diff
@@           Coverage Diff           @@
##           master    #1588   +/-   ##
=======================================
  Coverage   99.14%   99.14%           
=======================================
  Files         196      196           
  Lines       14245    14247    +2     
=======================================
+ Hits        14123    14125    +2     
  Misses        122      122           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1588?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/qnode.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1588/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3Fub2RlLnB5) | `98.69% <100.00%> (+<0.01%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1588?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1588?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Last update [67a5daf...5f3eee2](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1588?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

Hi @albi3ro what's the status on this?

> I decided to use a warning instead of a Error, as users may still want to calculate finite shot forward passes on an interface device.

Could they simply just evaluate the QNode without trying to differentiate it? If so, maybe we could refer to that in the error message.
@rmoyard  what do we need to add?
@albi3ro I think one the test using jax was deleted. `default_qubit_jax.py` line 269 more precisely we should add something covering this line.

Resolving patch:
diff --git a/pennylane/qnode.py b/pennylane/qnode.py
--- a/pennylane/qnode.py
+++ b/pennylane/qnode.py
@@ -372,6 +372,9 @@ def _validate_backprop_method(device, interface):
                 "Device caching is incompatible with the backprop diff_method"
             )
 
+        if device.shots is not None:
+            raise qml.QuantumFunctionError("Backpropagation is only supported when shots=None.")
+
         if backprop_interface is not None:
             # device supports backpropagation natively
 
@@ -384,30 +387,27 @@ def _validate_backprop_method(device, interface):
             )
 
         if backprop_devices is not None:
-            if device.shots is None:
-                # device is analytic and has child devices that support backpropagation natively
-
-                if interface in backprop_devices:
-                    # TODO: need a better way of passing existing device init options
-                    # to a new device?
-                    expand_fn = device.expand_fn
-                    batch_transform = device.batch_transform
-
-                    device = qml.device(
-                        backprop_devices[interface],
-                        wires=device.wires,
-                        shots=device.shots,
-                    )
-                    device.expand_fn = expand_fn
-                    device.batch_transform = batch_transform
-                    return "backprop", {}, device
-
-                raise qml.QuantumFunctionError(
-                    f"Device {device.short_name} only supports diff_method='backprop' when using the "
-                    f"{list(backprop_devices.keys())} interfaces."
+            # device is analytic and has child devices that support backpropagation natively
+
+            if interface in backprop_devices:
+                # TODO: need a better way of passing existing device init options
+                # to a new device?
+                expand_fn = device.expand_fn
+                batch_transform = device.batch_transform
+
+                device = qml.device(
+                    backprop_devices[interface],
+                    wires=device.wires,
+                    shots=device.shots,
                 )
+                device.expand_fn = expand_fn
+                device.batch_transform = batch_transform
+                return "backprop", {}, device
 
-            raise qml.QuantumFunctionError("Backpropagation is only supported when shots=None.")
+            raise qml.QuantumFunctionError(
+                f"Device {device.short_name} only supports diff_method='backprop' when using the "
+                f"{list(backprop_devices.keys())} interfaces."
+            )
 
         raise qml.QuantumFunctionError(
             f"The {device.short_name} device does not support native computations with "


Test patch:
diff --git a/tests/devices/test_default_qubit_jax.py b/tests/devices/test_default_qubit_jax.py
--- a/tests/devices/test_default_qubit_jax.py
+++ b/tests/devices/test_default_qubit_jax.py
@@ -170,10 +170,10 @@ def test_probs_jax(self, tol):
         dev = qml.device("default.qubit.jax", wires=1, shots=100)
         expected = jnp.array([0.0, 1.0])
 
-        @qml.qnode(dev, interface="jax")
+        @qml.qnode(dev, interface="jax", diff_method=None)
         def circuit():
             qml.PauliX(wires=0)
-            return qml.probs()
+            return qml.probs(wires=0)
 
         result = circuit()
         assert jnp.allclose(result, expected, atol=tol)
@@ -184,10 +184,10 @@ def test_probs_jax_jit(self, tol):
         expected = jnp.array([0.0, 1.0])
 
         @jax.jit
-        @qml.qnode(dev, interface="jax")
+        @qml.qnode(dev, interface="jax", diff_method=None)
         def circuit():
             qml.PauliX(wires=0)
-            return qml.probs()
+            return qml.probs(wires=0)
 
         result = circuit()
         assert jnp.allclose(result, expected, atol=tol)
@@ -198,10 +198,10 @@ def test_custom_shots_probs_jax_jit(self, tol):
         expected = jnp.array([[0.0, 1.0], [0.0, 1.0]])
 
         @jax.jit
-        @qml.qnode(dev, interface="jax")
+        @qml.qnode(dev, diff_method=None, interface="jax")
         def circuit():
             qml.PauliX(wires=0)
-            return qml.probs()
+            return qml.probs(wires=0)
 
         result = circuit()
         assert jnp.allclose(result, expected, atol=tol)
diff --git a/tests/test_qnode.py b/tests/test_qnode.py
--- a/tests/test_qnode.py
+++ b/tests/test_qnode.py
@@ -157,6 +157,14 @@ def test_validate_backprop_child_method_wrong_interface(self, monkeypatch):
         ):
             QNode._validate_backprop_method(dev, "another_interface")
 
+    @pytest.mark.parametrize("device_string", ("default.qubit", "default.qubit.autograd"))
+    def test_validate_backprop_finite_shots(self, device_string):
+        """Test that a device with finite shots cannot be used with backpropagation."""
+        dev = qml.device(device_string, wires=1, shots=100)
+
+        with pytest.raises(qml.QuantumFunctionError, match=r"Backpropagation is only supported"):
+            QNode._validate_backprop_method(dev, "autograd")
+
     def test_parameter_shift_qubit_device(self):
         """Test that the _validate_parameter_shift method
         returns the correct gradient transform for qubit devices."""