Repository: PennyLaneAI/pennylane
Pull request #7444 (2025-05-16 15:29:46)

[FTQC]Add Clifford commutation method to Pauli Tracking
### Before submitting

Please complete the following checklist when submitting a PR:

- [x] All new features must include a unit test.
      If you've fixed a bug or added code that should be tested, add a test to the
      test directory!

- [x] All new functions and code must be clearly commented and documented.
      If you do make documentation changes, make sure that the docs build and
      render correctly by running `make docs`.

- [x] Ensure that the test suite passes, by running `make test`.

- [x] Add a new entry to the `doc/releases/changelog-dev.md` file, summarizing the
      change, and including a link back to the PR.

- [x] The PennyLane source code conforms to
      [PEP8 standards](https://www.python.org/dev/peps/pep-0008/).
      We check all of our code against [Pylint](https://www.pylint.org/).
      To lint modified files, simply `pip install pylint`, and then
      run `pylint pennylane/path/to/file.py`.

When all the above are checked, delete everything above the dashed
line and fill in the pull request template.

------------------------------------------------------------------------------------------------------------

**Context:**

[sc-91490]

This PR implements the conjugate rules for a set of Clifford gates (`qml.H`, `qml.S`, `qml.CNOT`) and Paulis based on the fact that a Cliiford gate ($C$) can conjugate a Pauli to a new Pauli, i.e., $P_{new}C = CP_{old}$. 

**Conjugate rules**
- $CNOT$ gate

||$X_c$|$Z_c$|$X_t$|$Z_t$|
|:--:|:--:|:--:|:--:|:--:|
|$\pm$|$+$|$+$|$+$|$+$|
|$control$|$X$|$Z$|$I$|$Z$|
|$target$|$X$|$I$|$X$|$Z$|
 
- $Hadamard$ gate
 
||$X$|$Z$|
|:--:|:--:|:--:|
|$\pm$|$+$|$+$|
|$target$|$Z$|$X$|
 
- $S$ gate
 
||$X$|$Z$|
|:--:|:--:|:--:|
|$\pm$|$i$|$+$|
|$target$|$XZ$|$Z$|


**Description of the Change:**

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**


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.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7444?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
Attention: Patch coverage is `97.14286%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 99.69%. Comparing base [(`cd93e65`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/cd93e656383b3c8d2f6ab81e3abd4f77c8573ce0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`a8836ba`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/a8836ba2ccf61480a4935b2ca8628770dd3e734b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

| [Files with missing lines](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7444?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Patch % | Lines |
|---|---|---|
| [pennylane/ftqc/pauli\_tracker.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7444?src=pr&el=tree&filepath=pennylane%2Fftqc%2Fpauli_tracker.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2Z0cWMvcGF1bGlfdHJhY2tlci5weQ==) | 97.14% | [1 Missing :warning: ](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7444?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) |

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


```diff
@@                      Coverage Diff                       @@
##           add_xz_encode_pauli_merger    #7444      +/-   ##
==============================================================
- Coverage                       99.69%   99.69%   -0.01%     
==============================================================
  Files                             538      538              
  Lines                           51233    51267      +34     
==============================================================
+ Hits                            51078    51111      +33     
- Misses                            155      156       +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7444?dropdown=coverage&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).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/doc/code/qml_ftqc.rst b/doc/code/qml_ftqc.rst
index 6783cdabe..d320e9baa 100644
--- a/doc/code/qml_ftqc.rst
+++ b/doc/code/qml_ftqc.rst
@@ -26,4 +26,5 @@ This module contains functions for tracking, commuting Pauli operations in a Cli
 
     ~pauli_to_xz
     ~xz_to_pauli
-    ~pauli_prod
\ No newline at end of file
+    ~pauli_prod
+    ~commute_clifford_op
\ No newline at end of file
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 4ad7ed5f9..e3f604387 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -330,6 +330,10 @@
 * Add xz encoding related `pauli_to_xz`, `xz_to_pauli` and `pauli_prod` functions to the `ftqc` module.
   [(#7433)](https://github.com/PennyLaneAI/pennylane/pull/7433)
 
+* Add commutation rules for a Clifford gate set (`qml.H`, `qml.S`, `qml.CNOT`) to the `ftqc.pauli_tracker` module,
+  accessible via the `commute_clifford_op` function.
+  [(#7444)](https://github.com/PennyLaneAI/pennylane/pull/7444)
+
 * The `ftqc` module `measure_arbitrary_basis`, `measure_x` and `measure_y` functions
   can now be captured when program capture is enabled.
   [(#7219)](https://github.com/PennyLaneAI/pennylane/pull/7219)
diff --git a/pennylane/compiler/python_compiler/transforms/transform_interpreter/interpreter/impl.py b/pennylane/compiler/python_compiler/transforms/transform_interpreter/interpreter/impl.py
index e7f290d91..c5616a084 100644
--- a/pennylane/compiler/python_compiler/transforms/transform_interpreter/interpreter/impl.py
+++ b/pennylane/compiler/python_compiler/transforms/transform_interpreter/interpreter/impl.py
@@ -60,16 +60,13 @@ class TransformFunctionsExt(TransformFunctions):
         """Try to run the pass in xDSL, if it can't run on catalyst"""
 
         pass_name = op.pass_name.data  # pragma: no cover
-        requested_by_user = PipelinePass.build_pipeline_tuples(
-            self.passes, parse_pipeline.parse_pipeline(pass_name)
-        )
-
         try:
             # pragma: no cover
-            schedule = tuple(
-                pass_type.from_pass_spec(spec) for pass_type, spec in requested_by_user
+            pipeline = PipelinePass(
+                tuple(
+                    PipelinePass.iter_passes(self.passes, parse_pipeline.parse_pipeline(pass_name))
+                )
             )
-            pipeline = PipelinePass(schedule)
             pipeline.apply(self.ctx, args[0])
             return (args[0],)
         except:  # pylint: disable=bare-except
diff --git a/pennylane/ftqc/pauli_tracker.py b/pennylane/ftqc/pauli_tracker.py
index a64b2634d..7676fd16d 100644
--- a/pennylane/ftqc/pauli_tracker.py
+++ b/pennylane/ftqc/pauli_tracker.py
@@ -16,11 +16,10 @@ r"""
 This module contains Pauli Tracking functions.
 """
 
+import itertools
 from typing import List, Tuple
 
-import numpy as np
-
-from pennylane import I, X, Y, Z
+from pennylane import CNOT, H, I, S, X, Y, Z
 from pennylane.operation import Operator
 
 _OPS_TO_XZ = {
@@ -37,10 +36,10 @@ _XZ_TO_OPS = {
     (0, 1): Z,
 }
 
-_PAULIS = frozenset({X, Y, Z, I})
+_PAULIS = (X, Y, Z, I)
 
 
-def pauli_to_xz(op: Operator) -> Tuple[np.uint8, np.uint8]:
+def pauli_to_xz(op: Operator) -> Tuple[int, int]:
     r"""
     Convert a `Pauli` operator to its `xz` representation up to a global phase, i.e., :math:`encode_{xz}(Pauli)=(x,z)=X^xZ^z`, where
     :math:`x` is the exponent of the :class:`~pennylane.X` and :math:`z` is the exponent of
@@ -67,18 +66,22 @@ def pauli_to_xz(op: Operator) -> Tuple[np.uint8, np.uint8]:
         A xz tuple representation is returned for a given Pauli operator.
     """
 
-    if type(op) in _PAULIS:
+    if isinstance(op, _PAULIS):
         return _OPS_TO_XZ[type(op)]
-    raise NotImplementedError(f"{op.name} gate does not support xz encoding.")
 
+    if op in _PAULIS:
+        return _OPS_TO_XZ[op]
+
+    raise NotImplementedError(f"{type(op)} gate does not support xz encoding.")
 
-def xz_to_pauli(x: np.uint8, z: np.uint8) -> Operator:
+
+def xz_to_pauli(x: int, z: int) -> Operator:
     """
     Convert x, z to a Pauli operator class.
 
     Args:
-        x (np.uint8) : Exponent of :class:`~pennylane.X` in the Pauli record.
-        z (np.uint8) : Exponent of :class:`~pennylane.Z` in the Pauli record.
+        x (int) : Exponent of :class:`~pennylane.X` in the Pauli record.
+        z (int) : Exponent of :class:`~pennylane.Z` in the Pauli record.
 
     Return:
         A Pauli operator class.
@@ -99,15 +102,16 @@ def xz_to_pauli(x: np.uint8, z: np.uint8) -> Operator:
     raise ValueError("x and z should either 0 or 1.")
 
 
-def pauli_prod(ops: List[Operator]) -> Operator:
-    """
+def pauli_prod(ops: List[Operator]) -> Tuple[int, int]:
+    r"""
     Get the result of a product of a list of Pauli operators. The result is a new Pauli operator up to a global phase.
+    Mathematically, this function returns :math:`\prod_{i=0}^{n} ops[i]`.
 
     Args:
         ops (List[qml.operation.Operator]): A list of Pauli operators with the same target wire.
 
     Return:
-        A new Pauli operator.
+        A xz tuple representing a new Pauli operator.
 
     **Example:**
         The following example shows how the `pauli_prod` works.
@@ -117,24 +121,117 @@ def pauli_prod(ops: List[Operator]) -> Operator:
             from pennylane.ftqc.pauli_tracker import pauli_prod
             from pennylane import I, X, Y, Z
             >>> pauli_prod([I(0),X(0),Y(0),Z(0)])
-            I(0)
+            (0, 0)
 
-        A Pauli operator is returned for a list of Pauli operator up to a global phase.
+        The result is a new Pauli operator in the xz-encoding representation.
     """
-
     if len(ops) == 0:
         raise ValueError("Please ensure that a valid list of operators are passed to the method.")
-    op0 = ops.pop()
-    res_x, res_z = pauli_to_xz(op0)
-    op0_wire = op0.wires
-
-    while len(ops) > 0:
-        op = ops.pop()
-        wire = op.wires
-        if wire != op0_wire:
-            raise ValueError("All operators should target at the same wire.")
-        x, z = pauli_to_xz(op)
+    res_x, res_z = pauli_to_xz(ops[0])
+
+    for i in range(1, len(ops)):
+        x, z = pauli_to_xz(ops[i])
         res_x ^= x
         res_z ^= z
 
-    return xz_to_pauli(res_x, res_z)(wires=op0_wire)
+    return (res_x, res_z)
+
+
+def _commute_h(x: int, z: int):
+    r"""
+    Commute/move a Pauli represented by xz through :class:`~pennylane.H`.
+
+    Args:
+        x(int): Exponent of PauliX in the xz representation of a Pauli.
+        z(int): Exponent of PauliZ in the xz representation of a Pauli.
+
+    Return:
+        A list of a tuple of xz representing a new Pauli operation that the :class:`~pennylane.H` commutes to.
+    """
+    return [(z, x)]
+
+
+def _commute_s(x: int, z: int):
+    r"""
+    Commute/move a Pauli represented by xz through :class:`~pennylane.S`.
+
+    Args:
+        x(int): Exponent of PauliX in the xz representation of a Pauli.
+        z(int): Exponent of PauliZ in the xz representation of a Pauli.
+
+    Return:
+        A list of a tuple of xz representing a new Pauli operation that the :class:`~pennylane.S` commutes to.
+    """
+    return [(x, x ^ z)]
+
+
+def _commute_cnot(xc: int, zc: int, xt: int, zt: int):
+    r"""
+    Commute/move a Pauli represented by xz through :class:`~pennylane.CNOT`.
+
+    Args:
+        xc(int): Exponent of PauliX in the xz representation of a Pauli at the control wire.
+        zc(int): Exponent of PauliZ in the xz representation of a Pauli at the control wire.
+        xt(int): Exponent of PauliX in the xz representation of a Pauli at the target wire.
+        zt(int): Exponent of PauliZ in the xz representation of a Pauli at the target wire.
+
+    Return:
+        A list of xz tuples representing new Paulis operation that the :class:`~pennylane.cnot` commutes to.
+    """
+    return [(xc, zc ^ zt), (xc ^ xt, zt)]
+
+
+def commute_clifford_op(clifford_op: Operator, xz: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
+    r"""Gets the list of xz-encoded bits representing the list of input Pauli ops after being commuted through the given Clifford op.
+    Mathematically, this function applies the following equation: :math:`new\_xz \cdot clifford\_op = clifford\_op \cdot xz`
+    up to a global phase to move the :math:`xz` through the :math:`clifford\_op` and returns the :math:`new\_xz`. Note that :math:`xz` and
+    :math:`new\_xz` represent a list of Pauli operations.
+
+    Args:
+        clifford_op (Operator): A Clifford operator class. Supported operators are: :class:`qml.S`, :class:`qml.H`, :class:`qml.CNOT`.
+        xz (list(tuple)): A list of xz tuples which map to Pauli operators
+
+    Return:
+        A list of new xz tuples that the clifford_op commute the xz to.
+
+    **Example:**
+        The following example shows how the `commute_clifford_op` works.
+
+        .. code-block:: python3
+
+            from pennylane.ftqc.pauli_tracker import commute_clifford_op
+            from pennylane import I, CNOT
+            >>> commute_clifford_op(CNOT(wires=[0,1]), [(1, 1), (1, 0)])
+            [(1, 1), (0, 0)]
+
+        A list of Pauli operators in the xz representation is returned.
+    """
+    if len(xz) != clifford_op.num_wires:
+        raise ValueError(
+            "Please ensure that the length of xz matches the number of wires of the clifford_op."
+        )
+
+    if not all(len(element) == 2 for element in xz):
+        raise ValueError(
+            "Please ensure there are 2 elements instead of in each tuple in the xz list."
+        )
+
+    xz_flatten = tuple(itertools.chain.from_iterable(xz))
+
+    if not all(element in [0, 1] for element in xz_flatten):
+        raise ValueError("Please ensure xz are either 0 or 1.")
+
+    if isinstance(clifford_op, S):
+        _x, _z = xz[0]
+        return _commute_s(_x, _z)
+
+    if isinstance(clifford_op, H):
+        _x, _z = xz[0]
+        return _commute_h(_x, _z)
+
+    if isinstance(clifford_op, CNOT):
+        _xc, _zc = xz[0]
+        _xt, _zt = xz[1]
+        return _commute_cnot(_xc, _zc, _xt, _zt)
+
+    raise NotImplementedError("Only qml.H, qml.S and qml.CNOT are supported.")


Test patch:
diff --git a/.github/workflows/interface-unit-tests.yml b/.github/workflows/interface-unit-tests.yml
index f9cdc8447..046ef4a28 100644
--- a/.github/workflows/interface-unit-tests.yml
+++ b/.github/workflows/interface-unit-tests.yml
@@ -492,7 +492,7 @@ jobs:
       pytest_additional_args: ${{ needs.warnings-as-errors-setup.outputs.pytest_warning_args }}
       pytest_xml_file_path: '${{ inputs.job_name_prefix }}external-libraries-tests (${{ matrix.python-version }})${{ inputs.job_name_suffix }}.xml'
       additional_pip_packages: |
-        pyzx matplotlib stim quimb mitiq ply optax scipy-openblas32>=0.3.26 qualtran xdsl
+        pyzx matplotlib stim quimb mitiq ply optax scipy-openblas32>=0.3.26 qualtran xdsl==0.38
         git+https://github.com/PennyLaneAI/pennylane-qiskit.git@master
         ${{ needs.default-dependency-versions.outputs.jax-version }}
         ${{ needs.default-dependency-versions.outputs.tensorflow-version }}
diff --git a/tests/ftqc/test_pauli_tracker.py b/tests/ftqc/test_pauli_tracker.py
index 716b72d15..261fd376d 100644
--- a/tests/ftqc/test_pauli_tracker.py
+++ b/tests/ftqc/test_pauli_tracker.py
@@ -13,10 +13,24 @@
 # limitations under the License.
 """Unit tests for the ftqc.pauli_tracker module"""
 
+import random
+
+import numpy as np
 import pytest
 
 import pennylane as qml
-from pennylane.ftqc.pauli_tracker import pauli_prod, pauli_to_xz, xz_to_pauli
+from pennylane.ftqc.pauli_tracker import commute_clifford_op, pauli_prod, pauli_to_xz, xz_to_pauli
+
+_PAULIS = (qml.I, qml.X, qml.Y, qml.Z)
+
+
+def generate_pauli_list(wire: int, num_ops: int):
+    pauli_list = []
+
+    for _ in range(num_ops):
+        pauli_list.append(random.choice(_PAULIS)(wire))
+
+    return pauli_list
 
 
 class TestPauliTracker:
@@ -24,7 +38,16 @@ class TestPauliTracker:
 
     @pytest.mark.parametrize(
         "op, expected",
-        [(qml.I(0), (0, 0)), (qml.X(1), (1, 0)), (qml.Y(0), (1, 1)), (qml.Z(0), (0, 1))],
+        [
+            (qml.I(0), (0, 0)),
+            (qml.X(1), (1, 0)),
+            (qml.Y(0), (1, 1)),
+            (qml.Z(0), (0, 1)),
+            (qml.I, (0, 0)),
+            (qml.X, (1, 0)),
+            (qml.Y, (1, 1)),
+            (qml.Z, (0, 1)),
+        ],
     )
     def test_pauli_to_xz(self, op, expected):
         xz = pauli_to_xz(op)
@@ -47,39 +70,119 @@ class TestPauliTracker:
         with pytest.raises(ValueError):
             _ = xz_to_pauli(x, z)
 
+    @pytest.mark.parametrize("target_wire", [0, 10, 100, 1000])
+    @pytest.mark.parametrize("num_ops", [1, 10, 100, 1000])
+    def test_pauli_prod(self, target_wire, num_ops):
+        pauli_list = generate_pauli_list(target_wire, num_ops)
+
+        x, z = pauli_prod(pauli_list)
+
+        op = xz_to_pauli(x, z)(wires=target_wire)
+
+        op_simplify = qml.prod(*pauli_list).simplify()
+        expected_op = op_simplify if isinstance(op_simplify, _PAULIS) else op_simplify.terms()[1][0]
+
+        assert op == expected_op
+
+    @pytest.mark.parametrize("ops", [[]])
+    def test_pauli_prod_to_xz_unsupported_error(self, ops):
+        with pytest.raises(ValueError):
+            _ = pauli_prod(ops)
+
+    @pytest.mark.parametrize("clifford_op", [qml.S, qml.H])
+    @pytest.mark.parametrize("wires", [0, 1, 10, 100])
+    @pytest.mark.parametrize("pauli", [qml.I, qml.X, qml.Y, qml.Z])
+    def test_apply_clifford_ops_one_wire(self, clifford_op, wires, pauli):
+        pauli = pauli(wires=wires)
+        clifford_op = clifford_op(wires=wires)
+        xz = [pauli_to_xz(pauli)]
+        new_xz = commute_clifford_op(clifford_op, xz)
+        new_x, new_z = new_xz[0]
+        new_pauli = xz_to_pauli(new_x, new_z)(wires=wires)
+
+        new_xz_clifford_op = qml.prod(new_pauli, clifford_op).matrix()
+        clifford_op_xz = qml.prod(clifford_op, pauli).matrix()
+
+        assert np.allclose(new_xz_clifford_op, clifford_op_xz) | np.allclose(
+            new_xz_clifford_op, -clifford_op_xz
+        )
+
+    @pytest.mark.parametrize("clifford_op", [qml.CNOT])
+    @pytest.mark.parametrize("wires", [[0, 1], [1, 2], [10, 100]])
+    @pytest.mark.parametrize("pauli_control", [qml.I, qml.X, qml.Y, qml.Z])
+    @pytest.mark.parametrize("pauli_target", [qml.I, qml.X, qml.Y, qml.Z])
+    def test_apply_clifford_ops_two_wires(self, clifford_op, wires, pauli_control, pauli_target):
+        pauli_control = pauli_control(wires=wires[0])
+        pauli_target = pauli_target(wires=wires[1])
+        clifford_op = clifford_op(wires=wires)
+
+        xz = [pauli_to_xz(pauli_control), pauli_to_xz(pauli_target)]
+        new_xz = commute_clifford_op(clifford_op, xz)
+
+        _xc, _zc = new_xz[0]
+        new_pauli_control = xz_to_pauli(_xc, _zc)(wires=wires[0])
+        _xt, _zt = new_xz[1]
+        new_pauli_target = xz_to_pauli(_xt, _zt)(wires=wires[1])
+
+        new_xz_clifford_op = qml.prod(new_pauli_control, new_pauli_target, clifford_op).matrix()
+        clifford_op_xz = qml.prod(clifford_op, pauli_control, pauli_target).matrix()
+
+        assert np.allclose(new_xz_clifford_op, clifford_op_xz) | np.allclose(
+            new_xz_clifford_op, -clifford_op_xz
+        )
+
     @pytest.mark.parametrize(
-        "ops, expected",
+        "clifford_op", [qml.X(0), qml.RZ(phi=0.123, wires=0), qml.RX(phi=0.123, wires=0)]
+    )
+    @pytest.mark.parametrize("xz", [[(0, 1)]])
+    def test_apply_clifford_ops_not_imp(self, clifford_op, xz):
+        with pytest.raises(
+            NotImplementedError, match="Only qml.H, qml.S and qml.CNOT are supported."
+        ):
+            _ = commute_clifford_op(clifford_op, xz)
+
+    @pytest.mark.parametrize(
+        "clifford_op, xz",
         [
-            ([qml.I(0)], qml.I(0)),
-            ([qml.X(1)], qml.X(1)),
-            ([qml.Y(2)], qml.Y(2)),
-            ([qml.Z(3)], qml.Z(3)),
-            ([qml.I(0), qml.I(0)], qml.I(0)),
-            ([qml.I(1), qml.X(1)], qml.X(1)),
-            ([qml.I(2), qml.Y(2)], qml.Y(2)),
-            ([qml.I(3), qml.Z(3)], qml.Z(3)),
-            ([qml.X(0), qml.I(0)], qml.X(0)),
-            ([qml.X(1), qml.X(1)], qml.I(1)),
-            ([qml.X(2), qml.Y(2)], qml.Z(2)),
-            ([qml.X(3), qml.Z(3)], qml.Y(3)),
-            ([qml.Y(0), qml.I(0)], qml.Y(0)),
-            ([qml.Y(1), qml.X(1)], qml.Z(1)),
-            ([qml.Y(2), qml.Y(2)], qml.I(2)),
-            ([qml.Y(3), qml.Z(3)], qml.X(3)),
-            ([qml.Z(0), qml.I(0)], qml.Z(0)),
-            ([qml.Z(1), qml.X(1)], qml.Y(1)),
-            ([qml.Z(2), qml.Y(2)], qml.X(2)),
-            ([qml.Z(3), qml.Z(3)], qml.I(3)),
-            ([qml.X(4), qml.Y(4), qml.Z(4), qml.I(4), qml.Z(4)], qml.Z(4)),
+            (qml.S(0), [(0, 1), (1, 1)]),
+            (qml.CNOT(wires=[0, 1]), [(0, 1)]),
+            (qml.CNOT(wires=[0, 1]), [(0, 0), (0, 0), (0, 0)]),
         ],
     )
-    def test_pauli_prod(self, ops, expected):
-        op = pauli_prod(ops)
-        assert op == expected
+    def test_apply_clifford_ops_xz_list_size_mismatch(self, clifford_op, xz):
+        with pytest.raises(
+            ValueError,
+            match="Please ensure that the length of xz matches the number of wires of the clifford_op.",
+        ):
+            _ = commute_clifford_op(clifford_op, xz)
 
     @pytest.mark.parametrize(
-        "ops", [([]), ([qml.X(0), qml.I(1)]), ([qml.X(0), qml.Y(0), qml.Z(1)])]
+        "clifford_op, xz",
+        [
+            (qml.S(0), [(0, 1, 0)]),
+            (qml.CNOT(wires=[0, 1]), [(0, 1), (0, 1, 1)]),
+            (qml.CNOT(wires=[0, 1]), [(0, 0, 1), (0, 0)]),
+            (qml.CNOT(wires=[0, 1]), [(0, 0, 1), (0, 0, 1)]),
+        ],
     )
-    def test_pauli_prod_to_xz_unsupported_error(self, ops):
-        with pytest.raises(ValueError):
-            _ = pauli_prod(ops)
+    def test_apply_clifford_ops_xz_tuple_size_mismatch(self, clifford_op, xz):
+        with pytest.raises(
+            ValueError,
+            match="Please ensure there are 2 elements instead of in each tuple in the xz list.",
+        ):
+            _ = commute_clifford_op(clifford_op, xz)
+
+    @pytest.mark.parametrize(
+        "clifford_op, xz",
+        [
+            (qml.S(0), [(0, 2)]),
+            (qml.CNOT(wires=[0, 1]), [(0, 1), (0, 3)]),
+            (qml.CNOT(wires=[0, 1]), [(2, 0), (0, 0)]),
+        ],
+    )
+    def test_apply_clifford_ops_xz_value_err(self, clifford_op, xz):
+        with pytest.raises(
+            ValueError,
+            match="Please ensure xz are either 0 or 1.",
+        ):
+            _ = commute_clifford_op(clifford_op, xz)