Repository: PennyLaneAI/pennylane
Pull request #3389 (2022-11-18 15:08:06)

Add `pauli_sentence()` function which takes native PennyLane Ops to `PauliSentence` instance (PauliUtility #2)
**Context:**
`pauli_sentence` is a utility function which uses single dispatch to take existing PennyLane Operators and returns an instance of `PauliSentence` class.

**Description of the Change:**
- Update `is_pauli_word` to support instances of the hamiltonian class
- Introduce `pauli_sentence` function + tests 
- Fixed (independent) bug where calling for wires of an empty PauliWord caused errors in the to_mat method for PauliSentence

**Benefits:**
- Can now interface between native PennyLane ops and the new lightweight classes

codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3389?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#3389](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3389?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (81618bd) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/ffa6e46aae3ab1c94c8c9c8d2761a900ca91abd0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (ffa6e46) will **decrease** coverage by `0.00%`.
> The diff coverage is `98.92%`.

```diff
@@            Coverage Diff             @@
##           master    #3389      +/-   ##
==========================================
- Coverage   99.70%   99.69%   -0.01%     
==========================================
  Files         298      299       +1     
  Lines       26188    26265      +77     
==========================================
+ Hits        26110    26186      +76     
- Misses         78       79       +1     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3389?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/pauli/conversion.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3389/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3BhdWxpL2NvbnZlcnNpb24ucHk=) | `98.18% <98.18%> (ø)` | |
| [pennylane/ops/op\_math/exp.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3389/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9vcF9tYXRoL2V4cC5weQ==) | `100.00% <100.00%> (ø)` | |
| [pennylane/pauli/\_\_init\_\_.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3389/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3BhdWxpL19faW5pdF9fLnB5) | `100.00% <100.00%> (ø)` | |
| [pennylane/pauli/pauli\_arithmetic.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3389/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3BhdWxpL3BhdWxpX2FyaXRobWV0aWMucHk=) | `100.00% <100.00%> (ø)` | |
| [pennylane/pauli/utils.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3389/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3BhdWxpL3V0aWxzLnB5) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

Jaybsoni: [sc-28974]

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index b8b391df3..30dfd26b9 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -91,8 +91,26 @@
   ```
 
 * New basis sets, `6-311g` and `CC-PVDZ`, are added to the qchem basis set repo.
-  [(#3279)](https://github.com/PennyLaneAI/pennylane/pull/3279)
-  
+  [#3279](https://github.com/PennyLaneAI/pennylane/pull/3279)
+
+* New `pauli_sentence()` function which takes native `Operator` or `Hamiltonian`
+  instances representing a linear combination of Pauli words and returns 
+  the equivalent `PauliSentence`.
+  [(#3389)](https://github.com/PennyLaneAI/pennylane/pull/3389)
+
+  ```pycon
+  >>> op = 1.23 * qml.prod(qml.PauliX(wires=0), qml.PauliZ(wires=1))
+  >>> op
+  1.23*(PauliX(wires=[0]) @ PauliZ(wires=[1]))
+  >>> h = qml.Hamiltonian([1.23], [qml.PauliX(wires=0) @ qml.PauliZ(wires=1)])
+  >>> print(h)
+    (1.23) [X0 Z1]
+  >>> qml.pauli.pauli_sentence(op)
+  1.23 * Z(1) @ X(0)
+  >>> qml.pauli.pauli_sentence(h)
+  1.23 * X(0) @ Z(1)
+  ```
+
 * Added two new methods `operation()`, `hamiltonian()` for both `PauliSentence` and `PauliWord` classes to generate an equivalent PennyLane 
   `Operation` or `Hamiltonian` instance from a `PauliSentence` or `PauliWord` one.
   [(#3391)](https://github.com/PennyLaneAI/pennylane/pull/3391)
@@ -181,6 +199,9 @@
 
 <h3>Improvements</h3>
 
+* The `qml.is_pauli_word` now supports instances of `Hamiltonian`.
+  [(#3389)](https://github.com/PennyLaneAI/pennylane/pull/3389)
+
 * Support calling `qml.probs()`, `qml.counts()` and `qml.sample()` with no arguments to measure all
   wires. Calling any measurement with an empty wire list will raise an error.
   [#3299](https://github.com/PennyLaneAI/pennylane/pull/3299)
diff --git a/pennylane/pauli/__init__.py b/pennylane/pauli/__init__.py
index 40b0e6044..776747684 100644
--- a/pennylane/pauli/__init__.py
+++ b/pennylane/pauli/__init__.py
@@ -38,6 +38,10 @@ from .utils import (
     simplify,
 )
 
+from .conversion import (
+    pauli_sentence,
+)
+
 from .grouping import (
     group_observables,
     PauliGroupingStrategy,
diff --git a/pennylane/pauli/conversion.py b/pennylane/pauli/conversion.py
new file mode 100644
index 000000000..42d761237
--- /dev/null
+++ b/pennylane/pauli/conversion.py
@@ -0,0 +1,102 @@
+# Copyright 2018-2022 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Utility functions to convert between ``~.PauliSentence`` and other PennyLane operators.
+"""
+from functools import reduce, singledispatch
+
+from pennylane.operation import Tensor
+from pennylane.ops import Hamiltonian, Identity, PauliX, PauliY, PauliZ, Prod, SProd, Sum
+
+from .pauli_arithmetic import PauliWord, PauliSentence, X, Y, Z
+from .utils import is_pauli_word
+
+
+@singledispatch
+def pauli_sentence(op):
+    """Return the PauliSentence representation of an arithmetic operator or Hamiltonian.
+
+    Args:
+        op (~.Operator): The operator or Hamiltonian that needs to be converted.
+
+    Raises:
+        ValueError: Op must be a linear combination of Pauli operators
+
+    Returns ~.PauliSentence
+    """
+    raise ValueError(f"Op must be a linear combination of Pauli operators only, got: {op}")
+
+
+@pauli_sentence.register
+def _(op: PauliX):
+    return PauliSentence({PauliWord({op.wires[0]: X}): 1.0})
+
+
+@pauli_sentence.register
+def _(op: PauliY):
+    return PauliSentence({PauliWord({op.wires[0]: Y}): 1.0})
+
+
+@pauli_sentence.register
+def _(op: PauliZ):
+    return PauliSentence({PauliWord({op.wires[0]: Z}): 1.0})
+
+
+@pauli_sentence.register
+def _(op: Identity):  # pylint:disable=unused-argument
+    return PauliSentence({PauliWord({}): 1.0})
+
+
+@pauli_sentence.register
+def _(op: Tensor):
+    if not is_pauli_word(op):
+        raise ValueError(f"Op must be a linear combination of Pauli operators only, got: {op}")
+
+    factors = (pauli_sentence(factor) for factor in op.obs)
+    return reduce(lambda a, b: a * b, factors)
+
+
+@pauli_sentence.register
+def _(op: Prod):
+    factors = (pauli_sentence(factor) for factor in op)
+    return reduce(lambda a, b: a * b, factors)
+
+
+@pauli_sentence.register
+def _(op: SProd):
+    ps = pauli_sentence(op.base)
+    for pw, coeff in ps.items():
+        ps[pw] = coeff * op.scalar
+    return ps
+
+
+@pauli_sentence.register
+def _(op: Hamiltonian):
+    if not is_pauli_word(op):
+        raise ValueError(f"Op must be a linear combination of Pauli operators only, got: {op}")
+
+    summands = []
+    for coeff, term in zip(*op.terms()):
+        ps = pauli_sentence(term)
+        for pw, sub_coeff in ps.items():
+            ps[pw] = coeff * sub_coeff
+        summands.append(ps)
+
+    return reduce(lambda a, b: a + b, summands)
+
+
+@pauli_sentence.register
+def _(op: Sum):
+    summands = (pauli_sentence(summand) for summand in op)
+    return reduce(lambda a, b: a + b, summands)
diff --git a/pennylane/pauli/utils.py b/pennylane/pauli/utils.py
index f6c472f74..af5cb5093 100644
--- a/pennylane/pauli/utils.py
+++ b/pennylane/pauli/utils.py
@@ -1,4 +1,4 @@
-# Copyright 2018-2021 Xanadu Quantum Technologies Inc.
+# Copyright 2018-2022 Xanadu Quantum Technologies Inc.
 
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """
-Utility functions used in Pauli partitioning and measurement reduction schemes utilizing the
+Utility functions used in Pauli arithmetic, partitioning, and measurement reduction schemes utilizing the
 symplectic vector-space representation of Pauli words. For information on the symplectic binary
 representation of Pauli words and applications, see:
 
@@ -20,18 +20,18 @@ representation of Pauli words and applications, see:
 * `arXiv:1701.08213 <https://arxiv.org/abs/1701.08213>`_
 * `arXiv:1907.09386 <https://arxiv.org/abs/1907.09386>`_
 """
-import itertools
-from functools import reduce, lru_cache
+from itertools import product
 from typing import List
+from functools import reduce, lru_cache
 
 import numpy as np
-
 import pennylane as qml
-from pennylane.tape import OperationRecorder
-from pennylane.ops import Identity
-from pennylane.ops.qubit.non_parametric_ops import PauliX, PauliY, PauliZ
-from pennylane.operation import Observable, Tensor
+
 from pennylane.wires import Wires
+from pennylane.tape import OperationRecorder
+from pennylane.ops.qubit.hamiltonian import Hamiltonian
+from pennylane.ops import Identity, PauliX, PauliY, PauliZ
+from pennylane.operation import Tensor, Observable
 
 # To make this quicker later on
 ID_MAT = np.eye(2)
@@ -66,8 +66,7 @@ def is_pauli_word(observable):
 
 
     Args:
-        observable (Observable): an observable, either a :class:`~.Tensor` instance or
-            single-qubit observable.
+        observable (~.Observable): the operator to be examined
 
     Returns:
         bool: true if the input observable is a Pauli word, false otherwise.
@@ -81,7 +80,6 @@ def is_pauli_word(observable):
     >>> is_pauli_word(qml.PauliZ(0) @ qml.Hadamard(1))
     False
     """
-
     if not isinstance(observable, Observable):
         return False
 
@@ -89,6 +87,12 @@ def is_pauli_word(observable):
     if isinstance(observable, Tensor):
         return set(observable.name).issubset(pauli_word_names)
 
+    if isinstance(observable, Hamiltonian):
+        terms_pauli_word = []
+        for _, ob in zip(*observable.terms()):
+            terms_pauli_word.append(is_pauli_word(ob))
+        return all(terms_pauli_word)
+
     return observable.name in pauli_word_names
 
 
@@ -1044,12 +1048,12 @@ def partition_pauli_group(n_qubits: int) -> List[List[str]]:
     # ``['III', 'IIZ', 'IZI', 'IZZ', 'ZII', 'ZIZ', 'ZZI', 'ZZZ']``, which is our first group. The
     # next element of ``string`` will be ``('F', 'F', 'X')`` which we use to generate our second
     # group ``['IIX', 'IZX', 'ZIX', 'ZZX']``.
-    for string in itertools.product("FXYZ", repeat=n_qubits):
+    for string in product("FXYZ", repeat=n_qubits):
         if string not in strings:
             num_free_slots = string.count("F")
 
             group = []
-            commuting = itertools.product("IZ", repeat=num_free_slots)
+            commuting = product("IZ", repeat=num_free_slots)
 
             for commuting_string in commuting:
                 commuting_string = list(commuting_string)
@@ -1145,7 +1149,9 @@ def diagonalize_pauli_word(pauli_word):
     return diag_term
 
 
-def diagonalize_qwc_pauli_words(qwc_grouping):  # pylint: disable=too-many-branches
+def diagonalize_qwc_pauli_words(
+    qwc_grouping,
+):  # pylint: disable=too-many-branches, isinstance-second-argument-not-valid-type
     """Diagonalizes a list of mutually qubit-wise commutative Pauli words.
 
     Args:
@@ -1178,6 +1184,9 @@ def diagonalize_qwc_pauli_words(qwc_grouping):  # pylint: disable=too-many-branc
     if not are_pauli_words_qwc(qwc_grouping):
         raise ValueError("The list of Pauli words are not qubit-wise commuting.")
 
+    if not all(isinstance(op, (Tensor, PauliX, PauliY, PauliZ, Identity)) for op in qwc_grouping):
+        raise ValueError("This function only supports Tensor products of pauli ops.")
+
     pauli_operators = []
     diag_terms = []
 


Test patch:
diff --git a/tests/pauli/test_conversion.py b/tests/pauli/test_conversion.py
new file mode 100644
index 000000000..e46ae068a
--- /dev/null
+++ b/tests/pauli/test_conversion.py
@@ -0,0 +1,167 @@
+# Copyright 2018-2022 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Unit tests for utility functions of Pauli arithmetic."""
+
+import pytest
+
+import numpy as np
+import pennylane as qml
+from pennylane.pauli import pauli_sentence, PauliWord, PauliSentence
+
+
+test_hamiltonians = [
+    np.array([[2.5, -0.5], [-0.5, 2.5]]),
+    np.array(np.diag([0, 0, 0, 1])),
+    np.array([[-2, -2 + 1j, -2, -2], [-2 - 1j, 0, 0, -1], [-2, 0, -2, -1], [-2, -1, -1, 0]]),
+]
+
+
+class TestPauliSentence:
+    """Test the pauli_sentence function."""
+
+    pauli_op_ps = (
+        (qml.PauliX(wires=0), PauliSentence({PauliWord({0: "X"}): 1})),
+        (qml.PauliY(wires=1), PauliSentence({PauliWord({1: "Y"}): 1})),
+        (qml.PauliZ(wires="a"), PauliSentence({PauliWord({"a": "Z"}): 1})),
+        (qml.Identity(wires=0), PauliSentence({PauliWord({}): 1})),
+    )
+
+    @pytest.mark.parametrize("op, ps", pauli_op_ps)
+    def test_pauli_ops(self, op, ps):
+        """Test that PL Pauli ops are properly cast to a PauliSentence."""
+        assert pauli_sentence(op) == ps
+
+    tensor_ps = (
+        (
+            qml.PauliX(wires=0) @ qml.PauliZ(wires=1),
+            PauliSentence({PauliWord({0: "X", 1: "Z"}): 1}),
+        ),
+        (qml.PauliX(wires=0) @ qml.Identity(wires=1), PauliSentence({PauliWord({0: "X"}): 1})),
+        (
+            qml.PauliX(wires=0)
+            @ qml.PauliY(wires="a")
+            @ qml.PauliZ(wires=1)
+            @ qml.Identity(wires="b"),
+            PauliSentence({PauliWord({0: "X", "a": "Y", 1: "Z"}): 1}),
+        ),
+        (qml.PauliX(wires=0) @ qml.PauliY(wires=0), PauliSentence({PauliWord({0: "Z"}): 1j})),
+    )
+
+    @pytest.mark.parametrize("op, ps", tensor_ps)
+    def test_tensor(self, op, ps):
+        """Test that Tensors of Pauli ops are properly cast to a PauliSentence."""
+        assert pauli_sentence(op) == ps
+
+    def test_tensor_raises_error(self):
+        """Test that Tensors of non-Pauli ops raise error when cast to a PauliSentence."""
+        h_mat = np.array([[1, 1], [1, -1]])
+        h_op = qml.Hermitian(h_mat, wires=1)
+        op = qml.PauliX(wires=0) @ h_op
+
+        with pytest.raises(ValueError, match="Op must be a linear combination of"):
+            pauli_sentence(op)
+
+    hamiltonian_ps = (
+        (
+            qml.Hamiltonian([2], [qml.PauliZ(wires=0)]),
+            PauliSentence({PauliWord({0: "Z"}): 2}),
+        ),
+        (
+            qml.Hamiltonian(
+                [2, -0.5], [qml.PauliZ(wires=0), qml.PauliX(wires=0) @ qml.PauliZ(wires=1)]
+            ),
+            PauliSentence(
+                {
+                    PauliWord({0: "Z"}): 2,
+                    PauliWord({0: "X", 1: "Z"}): -0.5,
+                }
+            ),
+        ),
+        (
+            qml.Hamiltonian(
+                [2, -0.5, 3.14],
+                [
+                    qml.PauliZ(wires=0),
+                    qml.PauliX(wires=0) @ qml.PauliZ(wires="a"),
+                    qml.Identity(wires="b"),
+                ],
+            ),
+            PauliSentence(
+                {
+                    PauliWord({0: "Z"}): 2,
+                    PauliWord({0: "X", "a": "Z"}): -0.5,
+                    PauliWord({}): 3.14,
+                }
+            ),
+        ),
+    )
+
+    @pytest.mark.parametrize("op, ps", hamiltonian_ps)
+    def test_hamiltonian(self, op, ps):
+        """Test that a Hamiltonian is properly cast to a PauliSentence."""
+        assert pauli_sentence(op) == ps
+
+    operator_ps = (
+        (
+            qml.op_sum(qml.s_prod(2, qml.PauliZ(wires=0)), qml.PauliX(wires=1)),
+            PauliSentence({PauliWord({0: "Z"}): 2, PauliWord({1: "X"}): 1}),
+        ),
+        (
+            qml.op_sum(
+                qml.s_prod(2, qml.PauliZ(wires=0)),
+                -0.5 * qml.prod(qml.PauliX(wires=0), qml.PauliZ(wires=1)),
+            ),
+            PauliSentence(
+                {
+                    PauliWord({0: "Z"}): 2,
+                    PauliWord({0: "X", 1: "Z"}): -0.5,
+                }
+            ),
+        ),
+        (
+            qml.op_sum(
+                qml.s_prod(2, qml.PauliZ(wires=0)),
+                -0.5 * qml.prod(qml.PauliX(wires=0), qml.PauliZ(wires="a")),
+                qml.s_prod(2.14, qml.Identity(wires=0)),
+                qml.Identity(wires="a"),
+            ),
+            PauliSentence(
+                {
+                    PauliWord({0: "Z"}): 2,
+                    PauliWord({0: "X", "a": "Z"}): -0.5,
+                    PauliWord({}): 3.14,
+                }
+            ),
+        ),
+    )
+
+    @pytest.mark.parametrize("op, ps", operator_ps)
+    def test_operator(self, op, ps):
+        """Test that PL arithmetic op is properly cast to a PauliSentence."""
+        assert pauli_sentence(op) == ps
+
+    error_ps = (
+        qml.Hadamard(wires=0),
+        qml.Hamiltonian([1, 2], [qml.Projector([0], wires=0), qml.PauliZ(wires=1)]),
+        qml.RX(1.23, wires="a") + qml.PauliZ(wires=0),
+    )
+
+    @pytest.mark.parametrize("op", error_ps)
+    def test_error_not_linear_comb_pauli_words(self, op):
+        """Test that a ValueError is raised when trying to cast operators to a PauliSentence
+        which are not linear combinations of Pauli words."""
+        with pytest.raises(
+            ValueError, match="Op must be a linear combination of Pauli operators only, got:"
+        ):
+            pauli_sentence(op)
diff --git a/tests/pauli/test_pauli_utils.py b/tests/pauli/test_pauli_utils.py
index bbbb3de60..b5d7d186f 100644
--- a/tests/pauli/test_pauli_utils.py
+++ b/tests/pauli/test_pauli_utils.py
@@ -18,6 +18,7 @@ import pytest
 import numpy as np
 import pennylane as qml
 from pennylane import (
+    Hamiltonian,
     Identity,
     PauliX,
     PauliY,
@@ -260,18 +261,19 @@ class TestGroupingUtils:
 
         assert pytest.raises(ValueError, is_qwc, pauli_vec_1, pauli_vec_2)
 
-    def test_is_pauli_word(self):
-        """Test for determining whether input ``Observable`` instance is a Pauli word."""
-
-        observable_1 = PauliX(0)
-        observable_2 = PauliZ(1) @ PauliX(2) @ PauliZ(4)
-        observable_3 = PauliX(1) @ Hadamard(4)
-        observable_4 = Hadamard(0)
+    obs_pw_status = (
+        (PauliX(0), True),
+        (PauliZ(1) @ PauliX(2) @ PauliZ(4), True),
+        (PauliX(1) @ Hadamard(4), False),
+        (Hadamard(0), False),
+        (Hamiltonian([1.0, 0.5], [PauliX(0), PauliZ(1) @ PauliX(2)]), True),
+        (Hamiltonian([1.0, 0.5], [Hadamard(0), PauliZ(1) @ PauliX(2)]), False),
+    )
 
-        assert is_pauli_word(observable_1)
-        assert is_pauli_word(observable_2)
-        assert not is_pauli_word(observable_3)
-        assert not is_pauli_word(observable_4)
+    @pytest.mark.parametrize("ob, is_pw", obs_pw_status)
+    def test_is_pauli_word(self, ob, is_pw):
+        """Test for determining whether input ``Observable`` instance is a Pauli word."""
+        assert is_pauli_word(ob) == is_pw
 
     def test_is_pauli_word_non_observable(self):
         """Test that non-observables are not Pauli Words."""
@@ -898,6 +900,7 @@ class TestMeasurementTransformations:
         [PauliZ(0) @ Identity(1), PauliZ(0) @ PauliZ(1), PauliX(0) @ Identity(1)],
         [PauliX("a") @ PauliX(0), PauliZ(0) @ PauliZ("a")],
         [PauliZ("a") @ PauliY(1), PauliZ(1) @ PauliY("a")],
+        [Hamiltonian([1.0, 2.0], [PauliX("a"), PauliY("a")])],
     ]
 
     @pytest.mark.parametrize("not_qwc_grouping", not_qwc_groupings)