Repository: PennyLaneAI/pennylane

Issue:
[BUG] Inconsitency in qnode results between PennyLane versions.
### Expected behavior

The expected behavior is displayed by all PennyLane versions between 0.15 and 0.18 but the asserts fail with 0.19.0, 0.19.1, and 0.20.0 as indicated in the comments. This has been discussed verbally with  @josh146  and @mariaschuld .

### Actual behavior

The behavior of v0.15 to 0.18.

### Additional information

_No response_

### Source code

```shell
import pennylane as qml
import numpy as np

print(qml.__version__)

dev = qml.device('default.qubit', wires=8)

def ansatz(params):
    # if isinstance(params, qml.numpy.tensor):
    #     params = params.unwrap()
    print(type(params), params)
    qml.RY(2.0943951023931957, wires=[6])
    qml.RY(2.5261129449194057, wires=[4])
    qml.CNOT(wires=[6, 4])
    qml.RY(0.6154797086703873, wires=[4])
    qml.CNOT(wires=[6, 4])
    qml.RY(1.5707963267948966, wires=[0])
    qml.CNOT(wires=[4, 0])
    qml.CNOT(wires=[6, 0])
    qml.RY(-1.5707963267948966, wires=[0])
    qml.CNOT(wires=[4, 0])
    qml.CNOT(wires=[6, 0])
    qml.PauliZ(wires=[0])
    qml.PauliZ(wires=[4])
    qml.PauliZ(wires=[6])
    qml.PauliX(wires=[0])
    qml.CNOT(wires=[0, 5])
    qml.CNOT(wires=[0, 7])
    qml.PauliX(wires=[0])
    qml.PauliX(wires=[4])
    qml.CNOT(wires=[4, 1])
    qml.CNOT(wires=[4, 7])
    qml.PauliX(wires=[4])
    qml.PauliX(wires=[6])
    qml.CNOT(wires=[6, 1])
    qml.CNOT(wires=[6, 5])
    qml.PauliX(wires=[6])
    qml.SWAP(wires=[2, 4]),
    qml.SWAP(wires=[3, 5]),
    qml.CZ(wires=[2, 4]),
    qml.CZ(wires=[3, 5]),
    qml.DoubleExcitation(params[0], wires=[2, 3, 4, 5])
    qml.SWAP(wires=[0, 2]),
    qml.SWAP(wires=[1, 3]),
    qml.CZ(wires=[0, 2]),
    qml.CZ(wires=[1, 3]),
    qml.DoubleExcitation(params[1], wires=[0, 1, 2, 3])
    qml.SWAP(wires=[4, 6]),
    qml.SWAP(wires=[5, 7]),
    qml.CZ(wires=[4, 6]),
    qml.CZ(wires=[5, 7]),
    qml.DoubleExcitation(params[2], wires=[4, 5, 6, 7])
    qml.SWAP(wires=[2, 4]),
    qml.SWAP(wires=[3, 5]),
    qml.CZ(wires=[2, 4]),
    qml.CZ(wires=[3, 5]),
    qml.DoubleExcitation(params[3], wires=[2, 3, 4, 5])

params = np.array([-1.96429246e-02,
                   2.01105944e-05,
                   -7.99301913e-03,
                   2.92601909e-02])


def cost1(params1):
    @qml.qnode(dev, diff_method='parameter-shift')
    def qnode1(loca_params):
        ansatz(loca_params)
        return qml.state()

    ret = qnode1(params1)
    print(qnode1.qtape.operations)
    return ret

def cost2(params2):
    @qml.qnode(dev, diff_method='parameter-shift')
    def qnode2():
        ansatz(params2)
        return qml.state()

    ret = qnode2()
    print(qnode2.qtape.operations)
    return ret

res1 = cost1(params)
res2 = cost2(params)

print(repr(res1))
print(repr(res2))

assert np.allclose(res1, res2), res1 - res2 # passes with 0.15.0 through 0.18.0 and with >= 0.20.0 but fails with 0.19.0 and 0.19.1 (res2 remains correct, but res1 is wrong)


def cost3(params1):
    @qml.qnode(dev, diff_method='parameter-shift')
    def qnode3(loca_params):
        ansatz(loca_params)
        return [qml.expval(qml.PauliX(0)), qml.expval(qml.PauliX(1)), qml.expval(qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliX(3))]

    ret = qnode3(params1)
    print(qnode3.qtape.operations)
    return ret

res3 = cost3(params)

print(repr(res3))
assert np.allclose(res3, [0., 0.,  4.89587945e-01]) # passes with 0.15.0 through 0.18.0 but fails with 0.19.0, 0.19.1, and 0.20.0 with both diff_method='parameter-shift' and without, it works with qml.qnode_old.qnode()


# check with of res1 or res2 are wrong
expected_res12 = qml.numpy.tensor([8.32667268e-17+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        6.80649650e-03+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        7.04504103e-03+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        4.99969272e-01+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 1.11022302e-16+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 8.32667268e-17+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        2.82588467e-03+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        4.89578216e-01+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        7.14310823e-01+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j, 0.00000000e+00+0.j, 0.00000000e+00+0.j,
        0.00000000e+00+0.j], requires_grad=True)

assert np.allclose(res2, expected_res12)
assert np.allclose(res1, expected_res12) # passes with 0.15.0 through 0.18.0 and with >= 0.20.0 but fails with 0.19.0 and 0.19.1 (res2 remains correct, but res1 is wrong)
```


### Tracebacks

_No response_

### System information

```shell
This has been tested with different PL versions so this would be of little use.
```


### Existing GitHub issues

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


Discussion:

Resolving patch:
diff --git a/pennylane/_device.py b/pennylane/_device.py
--- a/pennylane/_device.py
+++ b/pennylane/_device.py
@@ -620,9 +620,11 @@ def default_expand_fn(self, circuit, max_expansion=10):
             .QuantumTape: The expanded/decomposed circuit, such that the device
             will natively support all operations.
         """
-        obs_on_same_wire = len(
-            circuit._obs_sharing_wires  # pylint: disable=protected-access
-        ) > 0 and not self.supports_observable("Hamiltonian")
+        # pylint: disable=protected-access
+        obs_on_same_wire = len(circuit._obs_sharing_wires) > 0
+        obs_on_same_wire &= not any(
+            isinstance(o, qml.Hamiltonian) for o in circuit._obs_sharing_wires
+        )
 
         ops_not_supported = not all(self.stopping_condition(op) for op in circuit.operations)
 


Test patch:
diff --git a/tests/test_qnode.py b/tests/test_qnode.py
--- a/tests/test_qnode.py
+++ b/tests/test_qnode.py
@@ -1130,3 +1130,43 @@ def circuit(x):
 
         qml.grad(circuit)(x)
         assert len(spy_expand.call_args_list) == 3
+
+    def test_expansion_multiple_qwc_observables(self, mocker):
+        """Test that the QNode correctly expands tapes that return
+        multiple measurements of commuting observables"""
+        dev = qml.device("default.qubit", wires=2)
+        obs = [qml.PauliX(0), qml.PauliX(0) @ qml.PauliY(1)]
+
+        @qml.qnode(dev)
+        def circuit(x, y):
+            qml.RX(x, wires=0)
+            qml.RY(y, wires=1)
+            return [qml.expval(o) for o in obs]
+
+        spy_expand = mocker.spy(circuit.device, "expand_fn")
+        params = [0.1, 0.2]
+        res = circuit(*params)
+
+        tape = spy_expand.spy_return
+        rotations, observables = qml.grouping.diagonalize_qwc_pauli_words(obs)
+
+        assert tape.observables[0].name == observables[0].name
+        assert tape.observables[1].name == observables[1].name
+
+        assert tape.operations[-2].name == rotations[0].name
+        assert tape.operations[-2].parameters == rotations[0].parameters
+        assert tape.operations[-1].name == rotations[1].name
+        assert tape.operations[-1].parameters == rotations[1].parameters
+
+        # check output value is consistent with a Hamiltonian expectation
+        coeffs = np.array([1.0, 1.0])
+        H = qml.Hamiltonian(coeffs, obs)
+
+        @qml.qnode(dev)
+        def circuit2(x, y):
+            qml.RX(x, wires=0)
+            qml.RY(y, wires=1)
+            return qml.expval(H)
+
+        res_H = circuit2(*params)
+        assert np.allclose(coeffs @ res, res_H)