Repository: PennyLaneAI/pennylane

Issue:
More informative error message needed for lack of support for parameter-broadcasting with `adjoint`
### Expected behavior

If a circuit with `diff_method = "adjoint"` is attempted to be broadcasted, the error message should say something to the effect that that isn't supported.

### Actual behavior

```
import pennylane as qml

dev = qml.device('default.qubit', wires=2)

@qml.qnode(dev, diff_method="adjoint")
def circuit(x):
    qml.RX(x, wires=0)
    return qml.expval(qml.PauliZ(0))

>>> circuit([1/2, 1/2, 1/2, 1/2])
ValueError: could not broadcast input array from shape (4,2,2) into shape (2,2)
```



### Additional information

Indirectly found from a forum post: https://discuss.pennylane.ai/t/parameter-broadcast-bug/2975/3

### Source code

```shell
import pennylane as qml

dev = qml.device('default.qubit', wires=2)

@qml.qnode(dev, diff_method="adjoint")
def circuit(x):
    qml.RX(x, wires=0)
    return qml.expval(qml.PauliZ(0))

circuit([1/2, 1/2, 1/2, 1/2])
```


### Tracebacks

```shell
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[10], line 8
      5     qml.RX(x, wires=0)
      6     return qml.expval(qml.PauliZ(0))
----> 8 circuit([1/2, 1/2, 1/2, 1/2])

File ~/pennylane-torch/lib/python3.9/site-packages/pennylane/qnode.py:867, in QNode.__call__(self, *args, **kwargs)
    865     self.execute_kwargs.pop("mode")
    866 # pylint: disable=unexpected-keyword-arg
--> 867 res = qml.execute(
    868     [self.tape],
    869     device=self.device,
    870     gradient_fn=self.gradient_fn,
    871     interface=self.interface,
    872     gradient_kwargs=self.gradient_kwargs,
    873     override_shots=override_shots,
    874     **self.execute_kwargs,
    875 )
    877 res = res[0]
    879 if old_interface == "auto":

File ~/pennylane-torch/lib/python3.9/site-packages/pennylane/interfaces/execution.py:483, in execute(tapes, device, gradient_fn, interface, grad_on_execution, gradient_kwargs, cache, cachesize, max_diff, override_shots, expand_fn, max_expansion, device_batch_transform)
    480     elif mapped_interface == "jax":
    481         _execute = _get_jax_execute_fn(interface, tapes)
...
-> 1939     bras[kk, ...] = self._apply_operation(ket, tape.observables[kk])
   1941 expanded_ops = []
   1942 for op in reversed(tape.operations):

ValueError: could not broadcast input array from shape (4,2,2) into shape (2,2)
```


### System information

```shell
Name: PennyLane
Version: 0.30.0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/XanaduAI/pennylane
Author:
Author-email:
License: Apache License 2.0
Location: /Users/isaac/pennylane-torch/lib/python3.9/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml
Required-by: PennyLane-Lightning

Platform info:           macOS-13.3.1-x86_64-i386-64bit
Python version:          3.9.14
Numpy version:           1.23.5
Scipy version:           1.10.1
Installed devices:
- default.gaussian (PennyLane-0.30.0)
- default.mixed (PennyLane-0.30.0)
- default.qubit (PennyLane-0.30.0)
- default.qubit.autograd (PennyLane-0.30.0)
- default.qubit.jax (PennyLane-0.30.0)
- default.qubit.tf (PennyLane-0.30.0)
- default.qubit.torch (PennyLane-0.30.0)
- default.qutrit (PennyLane-0.30.0)
- null.qubit (PennyLane-0.30.0)
- lightning.qubit (PennyLane-Lightning-0.30.0)
```


### Existing GitHub issues

- [X] I have searched existing GitHub issues to make sure the issue does not already exist.


Discussion:
Thanks @isaacdevlugt! I'm curious, I thought we had a fallback option in PL for devices that didn't support broadcasting 🤔 
The problem is that default qubit execution supports broadcasting, just not device differentiation.
Boils down to "preprocessing needs to be able to depend on the circuit and what we want to do to the circuit". 

Resolving patch:
diff --git a/pennylane/_qubit_device.py b/pennylane/_qubit_device.py
--- a/pennylane/_qubit_device.py
+++ b/pennylane/_qubit_device.py
@@ -1909,6 +1909,11 @@ def adjoint_jacobian(
             QuantumFunctionError: if the input tape has measurements that are not expectation values
                 or contains a multi-parameter operation aside from :class:`~.Rot`
         """
+        if tape.batch_size is not None:
+            raise qml.QuantumFunctionError(
+                "Parameter broadcasting is not supported with adjoint differentiation"
+            )
+
         # broadcasted inner product not summing over first dimension of b
         sum_axes = tuple(range(1, self.num_wires + 1))
         # pylint: disable=unnecessary-lambda-assignment


Test patch:
diff --git a/tests/legacy/test_qubit_device_adjoint_jacobian_old.py b/tests/legacy/test_qubit_device_adjoint_jacobian_old.py
--- a/tests/legacy/test_qubit_device_adjoint_jacobian_old.py
+++ b/tests/legacy/test_qubit_device_adjoint_jacobian_old.py
@@ -57,6 +57,21 @@ def test_finite_shots_warns(self):
         ):
             dev.adjoint_jacobian(tape)
 
+    def test_broadcasting_error(self):
+        """Tests that parameter broadcasting is not supported with adjoint on DefaultQubit"""
+        dev = qml.device("default.qubit", wires=1)
+
+        @qml.qnode(dev, diff_method="adjoint")
+        def circuit(x):
+            qml.RX(x, wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        with pytest.raises(
+            qml.QuantumFunctionError,
+            match="broadcasting is not supported with adjoint",
+        ):
+            circuit([0.1, 0.2])
+
     def test_unsupported_op(self, dev):
         """Test if a QuantumFunctionError is raised for an unsupported operation, i.e.,
         multi-parameter operations that are not qml.Rot"""