Repository: PennyLaneAI/pennylane
Pull request #5018 (2024-01-08 14:14:29)

[Pauli5] Add support in PauliWord and PauliSentence for qml.matrix 
Not dependent on previous Pauli PRs ([Pauli1](https://github.com/PennyLaneAI/pennylane/pull/4989), [Pauli2](https://github.com/PennyLaneAI/pennylane/pull/5001), [Pauli3](https://github.com/PennyLaneAI/pennylane/pull/5003), [Pauli4](https://github.com/PennyLaneAI/pennylane/pull/5017)).

Add support in `qml.matrix` to handle `PauliWord` and `PauliSentence`

github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.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.
Qottmann: [sc-53282]
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5018?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`c01cb4b`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/c01cb4b96f758a9f1a0c4e422115829b80e113cb?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.67% compared to head [(`c0532dd`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5018?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.66%.
> Report is 10 commits behind head on master.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #5018      +/-   ##
==========================================
- Coverage   99.67%   99.66%   -0.01%     
==========================================
  Files         394      394              
  Lines       35670    35393     -277     
==========================================
- Hits        35554    35276     -278     
- Misses        116      117       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5018?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).


Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 80b6ad285..44428e031 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -29,6 +29,9 @@
   [(#5003)](https://github.com/PennyLaneAI/pennylane/pull/5003)
   [(#5017)](https://github.com/PennyLaneAI/pennylane/pull/5017)
 
+* `qml.matrix` now accepts `PauliWord` and `PauliSentence` instances, `qml.matrix(PauliWord({0:"X"}))`.
+  [(#5018)](https://github.com/PennyLaneAI/pennylane/pull/5018)
+
 * Improve efficiency of matrix calculation when operator is symmetric over wires
    [(#3601)](https://github.com/PennyLaneAI/pennylane/pull/3601)
 
diff --git a/pennylane/ops/functions/matrix.py b/pennylane/ops/functions/matrix.py
index 3a8ca2600..f6f8da404 100644
--- a/pennylane/ops/functions/matrix.py
+++ b/pennylane/ops/functions/matrix.py
@@ -14,8 +14,8 @@
 """
 This module contains the qml.matrix function.
 """
-# pylint: disable=protected-access
-from typing import Sequence, Callable
+# pylint: disable=protected-access,too-many-branches
+from typing import Sequence, Callable, Union
 from functools import partial
 from warnings import warn
 
@@ -23,6 +23,8 @@ import pennylane as qml
 from pennylane.transforms.op_transforms import OperationTransformError
 from pennylane import transform
 from pennylane.typing import TensorLike
+from pennylane.operation import Operator
+from pennylane.pauli import PauliWord, PauliSentence
 
 _wire_order_none_warning = (
     "Calling qml.matrix() on a QuantumTape, quantum functions, or certain QNodes without "
@@ -32,11 +34,11 @@ _wire_order_none_warning = (
 )
 
 
-def matrix(op: qml.operation.Operator, wire_order=None) -> TensorLike:
+def matrix(op: Union[Operator, PauliWord, PauliSentence], wire_order=None) -> TensorLike:
     r"""The matrix representation of an operation or quantum circuit.
 
     Args:
-        op (Operator or QNode or QuantumTape or Callable): A quantum operator or quantum circuit.
+        op (Operator or QNode or QuantumTape or Callable or PauliWord or PauliSentence): A quantum operator or quantum circuit.
         wire_order (Sequence[Any], optional): Order of the wires in the quantum circuit.
             The default wire order depends on the type of ``op``:
 
@@ -51,7 +53,7 @@ def matrix(op: qml.operation.Operator, wire_order=None) -> TensorLike:
     Returns:
         TensorLike or qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]:
 
-        If an operator is provided as input, the matrix is returned directly in the form of a tensor.
+        If an operator, :class:`~PauliWord` or :class:`~PauliSentence` is provided as input, the matrix is returned directly in the form of a tensor.
         Otherwise, the transformed circuit is returned as described in :func:`qml.transform <pennylane.transform>`.
         Executing this circuit will provide its matrix representation.
 
@@ -85,6 +87,13 @@ def matrix(op: qml.operation.Operator, wire_order=None) -> TensorLike:
     .. details::
         :title: Usage Details
 
+        ``qml.matrix`` can also be used with :class:`~PauliWord` and :class:`~PauliSentence` instances.
+        Internally, we are using their ``to_mat()`` methods.
+
+        >>> X0 = PauliWord({0:"X"})
+        >>> np.allclose(qml.matrix(X0), X0.to_mat())
+        True
+
         ``qml.matrix`` can also be used with QNodes, tapes, or quantum functions that
         contain multiple operations.
 
@@ -175,7 +184,12 @@ def matrix(op: qml.operation.Operator, wire_order=None) -> TensorLike:
             wires specified, and this is the order in which wires appear in ``circuit()``.
 
     """
-    if not isinstance(op, qml.operation.Operator):
+    if not isinstance(op, Operator):
+        if isinstance(op, (PauliWord, PauliSentence)):
+            if wire_order is None and len(op.wires) > 1:
+                warn(_wire_order_none_warning, qml.PennyLaneDeprecationWarning)
+            return op.to_mat(wire_order=wire_order)
+
         if isinstance(op, qml.tape.QuantumScript):
             if wire_order is None and len(op.wires) > 1:
                 warn(_wire_order_none_warning, qml.PennyLaneDeprecationWarning)


Test patch:
diff --git a/tests/ops/functions/test_matrix.py b/tests/ops/functions/test_matrix.py
index 511f5d0da..8e1952940 100644
--- a/tests/ops/functions/test_matrix.py
+++ b/tests/ops/functions/test_matrix.py
@@ -25,6 +25,7 @@ from gate_data import I, X, Y, Z, H, S, CNOT, Rotx as RX, Roty as RY
 import pennylane as qml
 from pennylane import numpy as np
 from pennylane.transforms.op_transforms import OperationTransformError
+from pennylane.pauli import PauliWord, PauliSentence
 
 one_qubit_no_parameter = [
     qml.PauliX,
@@ -395,6 +396,62 @@ class TestCustomWireOrdering:
         expected_matrix = np.kron(RY(x), np.kron(X, Z))
         assert np.allclose(testcircuit2(x), expected_matrix)
 
+    @pytest.mark.parametrize("wire_order", [[0, 2, 1], [1, 0, 2], [2, 1, 0]])
+    def test_pauli_word_wireorder(self, wire_order):
+        """Test changing the wire order when using PauliWord"""
+        pw = PauliWord({0: "X", 1: "Y", 2: "Z"})
+        op = qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliZ(2)
+        assert qml.math.allclose(qml.matrix(pw, wire_order), qml.matrix(op, wire_order))
+
+    @pytest.mark.parametrize("wire_order", [[0, 2, 1], [1, 0, 2], [2, 1, 0]])
+    def test_pauli_sentence_wireorder(self, wire_order):
+        """Test changing the wire order when using PauliWord"""
+        pauli1 = PauliWord({0: "X", 1: "Y", 2: "Z"})
+        pauli2 = PauliWord({0: "Y", 1: "Z", 2: "X"})
+        ps = PauliSentence({pauli1: 1.5, pauli2: 0.5})
+
+        op1 = qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliZ(2)
+        op2 = qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliX(2)
+        op = 1.5 * op1 + 0.5 * op2
+        assert qml.math.allclose(qml.matrix(ps, wire_order), qml.matrix(op, wire_order))
+
+
+pw1 = PauliWord({0: "X", 1: "Z"})
+pw2 = PauliWord({0: "Y", 1: "Z"})
+pw3 = PauliWord({"a": "Y", "b": "Z"})
+op_pairs = (
+    (pw1, qml.prod(qml.PauliX(0), qml.PauliZ(1))),
+    (pw2, qml.prod(qml.PauliY(0), qml.PauliZ(1))),
+    # (pw3, qml.prod(qml.PauliY("a"), qml.PauliZ("b"))), # uncomment after fix 5041
+)
+
+
+class TestPauliWordPauliSentence:
+    @pytest.mark.parametrize("pw, op", op_pairs)
+    def test_PauliWord_matrix(self, pw, op):
+        """Test that a PauliWord is correctly transformed using qml.matrix"""
+        res = qml.matrix(pw)
+        true_res = qml.matrix(op)
+        assert qml.math.allclose(res, true_res)
+
+    @pytest.mark.parametrize("pw, op", op_pairs)
+    def test_PauliSentence_matrix(self, pw, op):
+        """Test that a PauliWord is correctly transformed using qml.matrix"""
+        res = qml.matrix(PauliSentence({pw: 0.5}))
+        true_res = qml.matrix(qml.s_prod(0.5, op))
+        assert qml.math.allclose(res, true_res)
+
+    @pytest.mark.xfail
+    def test_PauliSentence_matrix_xfail(self, pw, op):
+        """Test that a PauliWord is correctly transformed using qml.matrix"""
+        # needs fix https://github.com/PennyLaneAI/pennylane/pull/5041
+        # once fixed, uncomment the last line in op_pairs above, i.e. add
+        # (pw3, qml.prod(qml.PauliY("a"), qml.PauliZ("b"))),
+        pw, op = pw3, qml.prod(qml.PauliY("a"), qml.PauliZ("b"))
+        res = qml.matrix(PauliSentence({pw: 0.5}))
+        true_res = qml.matrix(qml.s_prod(0.5, op))
+        assert qml.math.allclose(res, true_res)
+
 
 class TestTemplates:
     """These tests are useful as they test operators that might not have
@@ -741,6 +798,18 @@ class TestMeasurements:
 class TestWireOrderDeprecation:
     """Test that wire_order=None is deprecated for the qml.matrix transform."""
 
+    def test_warning_pauli_word(self):
+        """Test that a warning is raised when calling qml.matrix without wire_order on a PauliWord"""
+        pw = PauliWord({0: "X", 1: "X"})
+        with pytest.warns(qml.PennyLaneDeprecationWarning, match=r"Calling qml\.matrix\(\) on"):
+            _ = qml.matrix(pw)
+
+    def test_warning_pauli_sentence(self):
+        """Test that a warning is raised when calling qml.matrix without wire_order on a PauliSentence"""
+        ps = PauliSentence({PauliWord({0: "X", 1: "X"}): 1.0})
+        with pytest.warns(qml.PennyLaneDeprecationWarning, match=r"Calling qml\.matrix\(\) on"):
+            _ = qml.matrix(ps)
+
     def test_warning_tape(self):
         """Test that a warning is raised when calling qml.matrix without wire_order on a tape."""
         qs = qml.tape.QuantumScript([qml.PauliX(1), qml.PauliX(0)])