Repository: PennyLaneAI/pennylane (python)

Issue:
[BUG] qml.matrix(op) and op.matrix() are different for Barrier and WireCut
### Expected behavior

I expected them both to pass, or both to fail.

### Actual behavior

`qml.matrix(qml.Barrier(0))` passed, `qml.Barrier(0).matrix()` failed. Same for `qml.WireCut`.

### Additional information

Attached source code is for Barrier, but the same thing happens with WireCut.

### Source code

```shell
>>> import pennylane as qml
>>> op = qml.Barrier(0)
>>> qml.matrix(op)
array([[1.]])
>>> op.matrix()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/matthews/src/github.com/PennyLaneAI/pennylane/pennylane/operation.py", line 1405, in matrix
    canonical_matrix = self.compute_matrix(*self.parameters, **self.hyperparameters)
  File "/Users/matthews/src/github.com/PennyLaneAI/pennylane/pennylane/operation.py", line 446, in compute_matrix
    raise MatrixUndefinedError
pennylane.operation.MatrixUndefinedError
```


### Tracebacks

_No response_

### System information

```shell
Name: PennyLane
Version: 0.27.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/matthews/.pyenv/versions/3.9.13/envs/pennylane/lib/python3.9/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, retworkx, scipy, semantic-version, toml
Required-by: PennyLane-Lightning

Platform info:           macOS-12.6.1-arm64-arm-64bit
Python version:          3.9.13
Numpy version:           1.23.2
Scipy version:           1.9.0
Installed devices:
- default.gaussian (PennyLane-0.27.0)
- default.mixed (PennyLane-0.27.0)
- default.qubit (PennyLane-0.27.0)
- default.qubit.autograd (PennyLane-0.27.0)
- default.qubit.jax (PennyLane-0.27.0)
- default.qubit.tf (PennyLane-0.27.0)
- default.qubit.torch (PennyLane-0.27.0)
- default.qutrit (PennyLane-0.27.0)
- null.qubit (PennyLane-0.27.0)
- lightning.qubit (PennyLane-Lightning-0.26.1)
```


### Existing GitHub issues

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

Pull request:
qml.matrix with tape expansion should fail if there are no operators
**Context:**
`qml.matrix(op)` would pass for Barrier and WireCut, whereas `op.matrix()` wouldn't. This was because `qml.matrix()` is wrapped with `op_transform` so it tries to expand a tape containing just that operation, and the expansion is validation (they have trivial decompositions).

**Description of the Change:**
`qml.matrix()` now fails if `op.matrix()` fails, and the subsequent effort at tape expansion results in a tape with no ~operations~ wires.

**Benefits:**
`qml.matrix(op)` and `op.matrix()` are now consistent. `qml.matrix()` works as expected.

**Possible Drawbacks:**
Maybe some operator without a matrix shouldn't fail? I think there are no drawbacks.

**Related GitHub Issues:**
Fixes #3330 

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 620b5e513..30f857897 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -89,6 +89,9 @@ Deprecations cycles are tracked at [doc/developement/deprecations.rst](https://d
   expansion now occurs.
   [(#3369)](https://github.com/PennyLaneAI/pennylane/pull/3369)
 
+* `qml.matrix(op)` now fails if the operator truly has no matrix (eg. `Barrier`) to match `op.matrix()`
+  [(#3386)](https://github.com/PennyLaneAI/pennylane/pull/3386)
+
 
 <h3>Contributors</h3>
 
diff --git a/pennylane/ops/functions/matrix.py b/pennylane/ops/functions/matrix.py
index 5a609c102..2712909ed 100644
--- a/pennylane/ops/functions/matrix.py
+++ b/pennylane/ops/functions/matrix.py
@@ -130,6 +130,8 @@ def matrix(op, *, wire_order=None):
 @matrix.tape_transform
 def _matrix(tape, wire_order=None):
     """Defines how matrix works if applied to a tape containing multiple operations."""
+    if not tape.wires:
+        raise qml.operation.MatrixUndefinedError
     params = tape.get_parameters(trainable_only=False)
     interface = qml.math.get_interface(*params)
 


Test patch:
diff --git a/tests/ops/functions/test_matrix.py b/tests/ops/functions/test_matrix.py
index 4a1b89093..00cdb7d09 100644
--- a/tests/ops/functions/test_matrix.py
+++ b/tests/ops/functions/test_matrix.py
@@ -689,3 +689,19 @@ class TestDifferentiation:
         assert isinstance(matrix, qml.numpy.tensor)
         assert np.allclose(l, 2 * np.cos(v / 2))
         assert np.allclose(dl, -np.sin(v / 2))
+
+
+class TestMeasurements:
+    @pytest.mark.parametrize(
+        "measurements,N",
+        [
+            ([qml.expval(qml.PauliX(0))], 2),
+            ([qml.probs(qml.PauliX(0)), qml.probs(qml.PauliZ(1))], 4),
+            ([qml.probs(wires=[0, 1])], 4),
+            ([qml.counts(wires=[0, 1, 2])], 8),
+        ],
+    )
+    def test_all_measurement_matrices_are_identity(self, measurements, N):
+        """Test that the matrix of a script with only observables is Identity."""
+        qscript = qml.tape.QuantumScript(measurements=measurements)
+        assert np.array_equal(qml.matrix(qscript), np.eye(N))
diff --git a/tests/ops/qubit/test_non_parametric_ops.py b/tests/ops/qubit/test_non_parametric_ops.py
index 9c0c04348..c887e888f 100644
--- a/tests/ops/qubit/test_non_parametric_ops.py
+++ b/tests/ops/qubit/test_non_parametric_ops.py
@@ -623,6 +623,14 @@ class TestBarrier:
         op = qml.Barrier(wires=(0, 1, 2, 3), only_visual=False)
         assert op.simplify() is op
 
+    def test_qml_matrix_fails(self):
+        """Test that qml.matrix(op) and op.matrix() both fail."""
+        op = qml.Barrier(0)
+        with pytest.raises(qml.operation.MatrixUndefinedError):
+            qml.matrix(op)
+        with pytest.raises(qml.operation.MatrixUndefinedError):
+            op.matrix()
+
 
 class TestWireCut:
     """Tests for the WireCut operator"""
@@ -653,6 +661,14 @@ class TestWireCut:
         ):
             qml.WireCut(wires=[])
 
+    def test_qml_matrix_fails(self):
+        """Test that qml.matrix(op) and op.matrix() both fail."""
+        op = qml.WireCut(0)
+        with pytest.raises(qml.operation.MatrixUndefinedError):
+            qml.matrix(op)
+        with pytest.raises(qml.operation.MatrixUndefinedError):
+            op.matrix()
+
 
 class TestMultiControlledX:
     """Tests for the MultiControlledX"""