Repository: PennyLaneAI/pennylane
Pull request #7549 (2025-05-30 21:34:06)

Add multi-qubit ResourceOperators
**Context:**
Added classes for multi-qubit ResourceOperator classes.

**Description of the Change:**

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**
[sc-91014]

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/7549?dropdown=coverage&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:
> Project coverage is 99.67%. Comparing base [(`8a55910`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/8a55910d37949e266f67cf8d5f6ecb89de44ec8c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`7c04f83`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/7c04f8378d7da2a4fcbb4d72175f58b3f5193432?dropdown=coverage&el=desc&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_resource_symbolic_no_rd    #7549   +/-   ##
============================================================
  Coverage                        99.67%   99.67%           
============================================================
  Files                              532      532           
  Lines                            52527    52527           
============================================================
  Hits                             52357    52357           
  Misses                             170      170           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7549?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/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 182ee299c..161d2d4fb 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -637,6 +637,10 @@
   estimation for symbolic operators of gates.
   [(#7584)](https://github.com/PennyLaneAI/pennylane/pull/7584)
 
+* Added the `pennylane.labs.ResourceOperator` templates which will be used to perform resource
+  estimation for multi-qubit parametic gates.
+  [(#7549)](https://github.com/PennyLaneAI/pennylane/pull/7549)
+
 * A new module :mod:`pennylane.labs.zxopt <pennylane.labs.zxopt>` provides access to the basic optimization
   passes from [pyzx](https://pyzx.readthedocs.io/en/latest/) for PennyLane circuits.
   
diff --git a/pennylane/labs/resource_estimation/__init__.py b/pennylane/labs/resource_estimation/__init__.py
index 8e0f25eb8..5db16ba04 100644
--- a/pennylane/labs/resource_estimation/__init__.py
+++ b/pennylane/labs/resource_estimation/__init__.py
@@ -103,6 +103,13 @@ Operators:
     ~ResourceCRot
     ~ResourceControlledPhaseShift
     ~ResourceTempAND
+    ~ResourceMultiRZ
+    ~ResourcePauliRot
+    ~ResourceIsingXX
+    ~ResourceIsingYY
+    ~ResourceIsingXY
+    ~ResourceIsingZZ
+    ~ResourcePSWAP
 
 """
 
@@ -154,6 +161,13 @@ from .ops import (
     ResourceCRZ,
     ResourceCRot,
     ResourceControlledPhaseShift,
+    ResourceMultiRZ,
+    ResourcePauliRot,
+    ResourceIsingXX,
+    ResourceIsingYY,
+    ResourceIsingXY,
+    ResourceIsingZZ,
+    ResourcePSWAP,
     ResourceTempAND,
     ResourceAdjoint,
     ResourceControlled,
diff --git a/pennylane/labs/resource_estimation/ops/__init__.py b/pennylane/labs/resource_estimation/ops/__init__.py
index 7efb423af..7ebf79ea7 100644
--- a/pennylane/labs/resource_estimation/ops/__init__.py
+++ b/pennylane/labs/resource_estimation/ops/__init__.py
@@ -50,4 +50,11 @@ from .qubit import (
     ResourceX,
     ResourceY,
     ResourceZ,
+    ResourceMultiRZ,
+    ResourcePauliRot,
+    ResourceIsingXX,
+    ResourceIsingYY,
+    ResourceIsingXY,
+    ResourceIsingZZ,
+    ResourcePSWAP,
 )
diff --git a/pennylane/labs/resource_estimation/ops/qubit/__init__.py b/pennylane/labs/resource_estimation/ops/qubit/__init__.py
index 2fb306edc..6d3574bb7 100644
--- a/pennylane/labs/resource_estimation/ops/qubit/__init__.py
+++ b/pennylane/labs/resource_estimation/ops/qubit/__init__.py
@@ -29,3 +29,12 @@ from .parametric_ops_single_qubit import (
     ResourceRY,
     ResourceRZ,
 )
+from .parametric_ops_multi_qubit import (
+    ResourceMultiRZ,
+    ResourcePauliRot,
+    ResourceIsingXX,
+    ResourceIsingYY,
+    ResourceIsingXY,
+    ResourceIsingZZ,
+    ResourcePSWAP,
+)
diff --git a/pennylane/labs/resource_estimation/ops/qubit/parametric_ops_multi_qubit.py b/pennylane/labs/resource_estimation/ops/qubit/parametric_ops_multi_qubit.py
new file mode 100644
index 000000000..dd922686e
--- /dev/null
+++ b/pennylane/labs/resource_estimation/ops/qubit/parametric_ops_multi_qubit.py
@@ -0,0 +1,1445 @@
+# Copyright 2025 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.
+r"""Resource operators for parametric multi qubit operations."""
+
+import pennylane.labs.resource_estimation as re
+from pennylane.labs.resource_estimation.resource_operator import (
+    CompressedResourceOp,
+    GateCount,
+    ResourceOperator,
+)
+
+# pylint: disable=arguments-differ
+
+
+class ResourceMultiRZ(ResourceOperator):
+    r"""Resource class for the MultiRZ gate.
+
+    Args:
+        num_wires (int): the number of qubits the operation acts upon
+        eps (float, optional): error threshold for Clifford+T decomposition of this operation
+        wires (Sequence[int], optional): the wires the operation acts on
+
+    Resources:
+        The resources come from Section VIII (Figure 3) of `The Bravyi-Kitaev transformation for
+        quantum computation of electronic structure <https://arxiv.org/pdf/1208.5986>`_ paper.
+
+        Specifically, the resources are given by one :class:`~.ResourceRZ` gate and a cascade of
+        :math:`2 * (n - 1)` :class:`~.ResourceCNOT` gates where :math:`n` is the number of qubits
+        the gate acts on.
+
+    .. seealso:: :class:`~.MultiRZ`
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> multi_rz = plre.ResourceMultiRZ(num_wires=3)
+    >>> gate_set = {"CNOT", "RZ"}
+    >>>
+    >>> print(plre.estimate_resources(multi_rz, gate_set))
+    --- Resources: ---
+    Total qubits: 3
+    Total gates : 5
+    Qubit breakdown:
+     clean qubits: 0, dirty qubits: 0, algorithmic qubits: 3
+    Gate breakdown:
+     {'CNOT': 4, 'RZ': 1}
+
+    """
+
+    resource_keys = {"num_wires", "eps"}
+
+    def __init__(self, num_wires, eps=None, wires=None) -> None:
+        self.num_wires = num_wires
+        self.eps = eps
+        super().__init__(wires=wires)
+
+    @classmethod
+    def default_resource_decomp(cls, num_wires, eps=None, **kwargs):
+        r"""Returns a list representing the resources for a controlled version of the operator.
+
+        Args:
+            num_wires (int): the number of qubits the operation acts upon
+            eps (float): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The resources come from Section VIII (Figure 3) of `The Bravyi-Kitaev transformation for
+            quantum computation of electronic structure <https://arxiv.org/pdf/1208.5986>`_ paper.
+
+            Specifically, the resources are given by one :class:`~.ResourceRZ` gate and a cascade of
+            :math:`2 * (n - 1)` :class:`~.ResourceCNOT` gates where :math:`n` is the number of qubits
+            the gate acts on.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        cnot = re.ResourceCNOT.resource_rep()
+        rz = re.ResourceRZ.resource_rep(eps=eps)
+
+        return [GateCount(cnot, 2 * (num_wires - 1)), GateCount(rz)]
+
+    @property
+    def resource_params(self):
+        r"""Returns a dictionary containing the minimal information needed to compute the resources.
+
+        Returns:
+            dict: A dictionary containing the resource parameters:
+            * num_wires (int): the number of qubits the operation acts upon
+            * eps (float): error threshold for clifford plus T decomposition of this operation
+        """
+        return {"num_wires": self.num_wires, "eps": self.eps}
+
+    @classmethod
+    def resource_rep(cls, num_wires, eps=None):
+        """Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation.
+
+        Args:
+            num_wires (int): the number of qubits the operation acts upon
+            eps (float): error threshold for clifford plus T decomposition of this operation
+
+        Returns:
+            CompressedResourceOp: the operator in a compressed representation
+        """
+        return CompressedResourceOp(cls, {"num_wires": num_wires, "eps": eps})
+
+    @classmethod
+    def default_adjoint_resource_decomp(cls, num_wires, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for the adjoint of the operator.
+
+        Args:
+            num_wires (int): the number of qubits the operation acts upon
+            eps (float): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The adjoint of this operator just changes the sign of the phase, thus
+            the resources of the adjoint operation results in the original operation.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        return [GateCount(cls.resource_rep(num_wires=num_wires, eps=eps))]
+
+    @classmethod
+    def default_controlled_resource_decomp(
+        cls,
+        ctrl_num_ctrl_wires,
+        ctrl_num_ctrl_values,
+        num_wires,
+        eps=None,
+    ) -> list[GateCount]:
+        r"""Returns a list representing the resources for a controlled version of the operator.
+
+        Args:
+            ctrl_num_ctrl_wires (int): the number of qubits the operation is controlled on
+            ctrl_num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state
+            num_wires (int): the number of qubits the base operation acts upon
+            eps (float): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The resources are derived from the following identity. If an operation :math:`\hat{A}`
+            can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}`
+            then the controlled operation :math:`C\hat{A}` can be expressed as:
+
+            .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger}
+
+            Specifically, the resources are one multi-controlled RZ-gate and a cascade of
+            :math:`2 * (n - 1)` :class:`~.ResourceCNOT` gates where :math:`n` is the number of qubits
+            the gate acts on.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        cnot = re.resource_rep(re.ResourceCNOT)
+        ctrl_rz = re.resource_rep(
+            re.ResourceControlled,
+            {
+                "base_cmpr_op": re.resource_rep(re.ResourceRZ, {"eps": eps}),
+                "num_ctrl_wires": ctrl_num_ctrl_wires,
+                "num_ctrl_values": ctrl_num_ctrl_values,
+            },
+        )
+
+        return [GateCount(cnot, 2 * (num_wires - 1)), GateCount(ctrl_rz)]
+
+    @classmethod
+    def default_pow_resource_decomp(cls, pow_z, num_wires, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for an operator raised to a power.
+
+        Args:
+            pow_z (int): the power that the operator is being raised to
+            num_wires (int): the number of qubits the base operation acts upon
+            eps (float): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            Taking arbitrary powers of a general rotation produces a sum of rotations.
+            The resources simplify to just one total multi-RZ rotation.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        return [GateCount(cls.resource_rep(num_wires=num_wires, eps=eps))]
+
+
+class ResourcePauliRot(ResourceOperator):
+    r"""Resource class for the PauliRot gate.
+
+    Args:
+        pauli_string (str): a string describing the pauli operators that define the rotation
+        eps (float, optional): error threshold for clifford plus T decomposition of this operation
+        wires (Sequence[int], optional): the wire the operation acts on
+
+    Resources:
+        When the :code:`pauli_string` is a single Pauli operator (:code:`X, Y, Z, Identity`)
+        the cost is the associated single qubit rotation (:code:`RX, RY, RZ, GlobalPhase`).
+
+        The resources come from Section VIII (Figures 3 & 4) of `The Bravyi-Kitaev transformation
+        for quantum computation of electronic structure <https://arxiv.org/pdf/1208.5986>`_ paper,
+        in combination with the following identity:
+
+        .. math::
+
+            \begin{align}
+                \hat{X} &= \hat{H} \cdot \hat{Z} \cdot \hat{H}, \\
+                \hat{Y} &= \hat{S} \cdot \hat{H} \cdot \hat{Z} \cdot \hat{H} \cdot \hat{S}^{\dagger}.
+            \end{align}
+
+        Specifically, the resources are given by one :class:`~.ResourceRZ` gate and a cascade of
+        :math:`2 * (n - 1)` :class:`~.ResourceCNOT` gates where :math:`n` is the number of qubits
+        the gate acts on. Additionally, for each :code:`X` gate in the Pauli word we conjugate by
+        a pair of :class:`~.ResourceHadamard` gates, and for each :code:`Y` gate in the Pauli word we
+        conjugate by a pair of :class:`~.ResourceHadamard` and a pair of :class:`~.ResourceS` gates.
+
+    .. seealso:: :class:`~.PauliRot`
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> pr = plre.ResourcePauliRot(pauli_string="XYZ")
+    >>> print(plre.estimate_resources(pr, plre.StandardGate\
+    Set))
+    --- Resources: ---
+    Total qubits: 3
+    Total gates : 11
+    Qubit breakdown:
+     clean qubits: 0, dirty qubits: 0, algorithmic qubits: 3
+    Gate breakdown:
+     {'Hadamard': 4, 'S': 1, 'Adjoint(S)': 1, 'RZ': 1, 'CNOT': 4}
+
+    """
+
+    resource_keys = {"pauli_string", "eps"}
+
+    def __init__(self, pauli_string, eps=None, wires=None) -> None:
+        self.eps = eps
+        self.pauli_string = pauli_string
+        self.num_wires = len(pauli_string)
+        super().__init__(wires=wires)
+
+    @classmethod
+    def default_resource_decomp(cls, pauli_string, eps=None, **kwargs):
+        r"""Returns a list of GateCount objects representing the operator's resources.
+
+        Args:
+            pauli_string (str): a string describing the pauli operators that define the rotation
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            When the :code:`pauli_string` is a single Pauli operator (:code:`X, Y, Z, Identity`)
+            the cost is the associated single qubit rotation (:code:`RX, RY, RZ, GlobalPhase`).
+
+            The resources come from Section VIII (Figures 3 & 4) of `The Bravyi-Kitaev transformation
+            for quantum computation of electronic structure <https://arxiv.org/pdf/1208.5986>`_ paper,
+            in combination with the following identity:
+
+            .. math::
+
+                \begin{align}
+                    \hat{X} &= \hat{H} \cdot \hat{Z} \cdot \hat{H}, \\
+                    \hat{Y} &= \hat{S} \cdot \hat{H} \cdot \hat{Z} \cdot \hat{H} \cdot \hat{S}^{\dagger}.
+                \end{align}
+
+            Specifically, the resources are given by one :class:`~.ResourceRZ` gate and a cascade of
+            :math:`2 * (n - 1)` :class:`~.ResourceCNOT` gates where :math:`n` is the number of qubits
+            the gate acts on. Additionally, for each :code:`X` gate in the Pauli word we conjugate by
+            a pair of :class:`~.ResourceHadamard` gates, and for each :code:`Y` gate in the Pauli word we
+            conjugate by a pair of :class:`~.ResourceHadamard` and a pair of :class:`~.ResourceS` gates.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        if (set(pauli_string) == {"I"}) or (len(pauli_string) == 0):
+            gp = re.resource_rep(re.ResourceGlobalPhase)
+            return [GateCount(gp)]
+
+        if pauli_string == "X":
+            return [GateCount(re.resource_rep(re.ResourceRX, {"eps": eps}))]
+        if pauli_string == "Y":
+            return [GateCount(re.resource_rep(re.ResourceRY, {"eps": eps}))]
+        if pauli_string == "Z":
+            return [GateCount(re.resource_rep(re.ResourceRZ, {"eps": eps}))]
+
+        active_wires = len(pauli_string.replace("I", ""))
+
+        h = re.resource_rep(re.ResourceHadamard)
+        s = re.resource_rep(re.ResourceS)
+        rz = re.resource_rep(re.ResourceRZ, {"eps": eps})
+        s_dagg = re.resource_rep(
+            re.ResourceAdjoint,
+            {"base_cmpr_op": re.resource_rep(re.ResourceS)},
+        )
+        cnot = re.resource_rep(re.ResourceCNOT)
+
+        h_count = 0
+        s_count = 0
+
+        for gate in pauli_string:
+            if gate == "X":
+                h_count += 1
+            if gate == "Y":
+                h_count += 1
+                s_count += 1
+
+        gate_types = []
+        if h_count:
+            gate_types.append(GateCount(h, 2 * h_count))
+
+        if s_count:
+            gate_types.append(GateCount(s, s_count))
+            gate_types.append(GateCount(s_dagg, s_count))
+
+        gate_types.append(GateCount(rz))
+        gate_types.append(GateCount(cnot, 2 * (active_wires - 1)))
+
+        return gate_types
+
+    @property
+    def resource_params(self):
+        r"""Returns a dictionary containing the minimal information needed to compute the resources.
+
+        Returns:
+            dict: A dictionary containing the resource parameters:
+            * pauli_string (str): a string describing the pauli operators that define the rotation
+            * eps (float): error threshold for clifford plus T decomposition of this operation
+        """
+        return {
+            "pauli_string": self.pauli_string,
+            "eps": self.eps,
+        }
+
+    @classmethod
+    def resource_rep(cls, pauli_string, eps=None):
+        """Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation.
+
+        Args:
+            pauli_string (str): a string describing the pauli operators that define the rotation
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Returns:
+            CompressedResourceOp: the operator in a compressed representation
+        """
+        return CompressedResourceOp(cls, {"pauli_string": pauli_string, "eps": eps})
+
+    @classmethod
+    def default_adjoint_resource_decomp(cls, pauli_string, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for the adjoint of the operator.
+
+        Args:
+            pauli_string (str): a string describing the pauli operators that define the rotation
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The adjoint of this operator just changes the sign of the phase, thus
+            the resources of the adjoint operation results in the original operation.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        return [GateCount(cls.resource_rep(pauli_string=pauli_string, eps=eps))]
+
+    @classmethod
+    def default_controlled_resource_decomp(
+        cls,
+        ctrl_num_ctrl_wires,
+        ctrl_num_ctrl_values,
+        pauli_string,
+        eps=None,
+    ) -> list[GateCount]:
+        r"""Returns a list representing the resources for a controlled version of the operator.
+
+        Args:
+            ctrl_num_ctrl_wires (int): the number of qubits the operation is controlled on
+            ctrl_num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state
+            pauli_string (str): a string describing the pauli operators that define the rotation
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            When the :code:`pauli_string` is a single Pauli operator (:code:`X, Y, Z, Identity`)
+            the cost is the associated controlled single qubit rotation gate: (:class:`~.ResourceCRX`,
+            :class:`~.ResourceCRY`, :class:`~.ResourceCRZ`, controlled-:class:`~.ResourceGlobalPhase`).
+
+            The resources are derived from the following identity. If an operation :math:`\hat{A}`
+            can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}`
+            then the controlled operation :math:`C\hat{A}` can be expressed as:
+
+            .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger}
+
+            Specifically, the resources are one multi-controlled RZ-gate and a cascade of
+            :math:`2 * (n - 1)` :class:`~.ResourceCNOT` gates where :math:`n` is the number of qubits
+            the gate acts on. Additionally, for each :code:`X` gate in the Pauli word we conjugate by
+            a pair of :class:`~.ResourceHadamard` gates, and for each :code:`Y` gate in the Pauli word
+            we conjugate by a pair of :class:`~.ResourceHadamard` and a pair of :class:`~.ResourceS` gates.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+
+        if (set(pauli_string) == {"I"}) or (len(pauli_string) == 0):
+            ctrl_gp = re.ResourceControlled.resource_rep(
+                re.resource_rep(re.ResourceGlobalPhase),
+                ctrl_num_ctrl_wires,
+                ctrl_num_ctrl_values,
+            )
+            return [GateCount(ctrl_gp)]
+
+        if pauli_string == "X":
+            return [
+                GateCount(
+                    re.ResourceControlled.resource_rep(
+                        re.resource_rep(re.ResourceRX, {"eps": eps}),
+                        ctrl_num_ctrl_wires,
+                        ctrl_num_ctrl_values,
+                    )
+                )
+            ]
+        if pauli_string == "Y":
+            return [
+                GateCount(
+                    re.ResourceControlled.resource_rep(
+                        re.resource_rep(re.ResourceRY, {"eps": eps}),
+                        ctrl_num_ctrl_wires,
+                        ctrl_num_ctrl_values,
+                    )
+                )
+            ]
+        if pauli_string == "Z":
+            return [
+                GateCount(
+                    re.ResourceControlled.resource_rep(
+                        re.resource_rep(re.ResourceRZ, {"eps": eps}),
+                        ctrl_num_ctrl_wires,
+                        ctrl_num_ctrl_values,
+                    )
+                )
+            ]
+
+        active_wires = len(pauli_string.replace("I", ""))
+
+        h = re.ResourceHadamard.resource_rep()
+        s = re.ResourceS.resource_rep()
+        crz = re.ResourceControlled.resource_rep(
+            re.resource_rep(re.ResourceRZ, {"eps": eps}),
+            ctrl_num_ctrl_wires,
+            ctrl_num_ctrl_values,
+        )
+        s_dagg = re.resource_rep(
+            re.ResourceAdjoint,
+            {"base_cmpr_op": re.resource_rep(re.ResourceS)},
+        )
+        cnot = re.ResourceCNOT.resource_rep()
+
+        h_count = 0
+        s_count = 0
+
+        for gate in pauli_string:
+            if gate == "X":
+                h_count += 1
+            if gate == "Y":
+                h_count += 1
+                s_count += 1
+
+        gate_types = []
+        if h_count:
+            gate_types.append(GateCount(h, 2 * h_count))
+
+        if s_count:
+            gate_types.append(GateCount(s, s_count))
+            gate_types.append(GateCount(s_dagg, s_count))
+
+        gate_types.append(GateCount(crz))
+        gate_types.append(GateCount(cnot, 2 * (active_wires - 1)))
+
+        return gate_types
+
+    @classmethod
+    def default_pow_resource_decomp(cls, pow_z, pauli_string, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for an operator raised to a power.
+
+        Args:
+            pow_z (int): the power that the operator is being raised to
+            pauli_string (str): a string describing the pauli operators that define the rotation
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            Taking arbitrary powers of a general rotation produces a sum of rotations.
+            The resources simplify to just one total pauli rotation.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        return [GateCount(cls.resource_rep(pauli_string=pauli_string, eps=eps))]
+
+
+class ResourceIsingXX(ResourceOperator):
+    r"""Resource class for the IsingXX gate.
+
+    Args:
+        eps (float, optional): error threshold for Clifford+T decomposition of this operation
+        wires (Sequence[int], optional): the wire the operation acts on
+
+    Resources:
+        Ising XX coupling gate
+
+        .. math:: XX(\phi) = \exp\left(-i \frac{\phi}{2} (X \otimes X)\right) =
+            \begin{bmatrix} =
+                \cos(\phi / 2) & 0 & 0 & -i \sin(\phi / 2) \\
+                0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\
+                0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\
+                -i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2)
+            \end{bmatrix}.
+
+        The circuit implementing this transformation is given by:
+
+        .. code-block:: bash
+
+            0: ─╭●─────RX────╭●─┤
+            1: ─╰X───────────╰X─┤
+
+    .. seealso:: :class:`~.IsingXX`
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> ising_xx = plre.ResourceIsingXX()
+    >>> gate_set = {"CNOT", "RX"}
+    >>> print(plre.estimate_resources(ising_xx, gate_set))
+    --- Resources: ---
+    Total qubits: 2
+    Total gates : 3
+    Qubit breakdown:
+     clean qubits: 0, dirty qubits: 0, algorithmic qubits: 2
+    Gate breakdown:
+     {'CNOT': 2, 'RX': 1}
+
+    """
+
+    num_wires = 2
+    resource_keys = {"eps"}
+
+    def __init__(self, eps=None, wires=None) -> None:
+        self.eps = eps
+        super().__init__(wires=wires)
+
+    @classmethod
+    def default_resource_decomp(cls, eps=None, **kwargs):
+        r"""Returns a list of GateCount objects representing the operator's resources.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            Ising XX coupling gate
+
+            .. math:: XX(\phi) = \exp\left(-i \frac{\phi}{2} (X \otimes X)\right) =
+                \begin{bmatrix} =
+                    \cos(\phi / 2) & 0 & 0 & -i \sin(\phi / 2) \\
+                    0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\
+                    0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\
+                    -i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2)
+                \end{bmatrix}.
+
+            The cost for implementing this transformation is given by:
+
+            .. code-block:: bash
+
+                0: ─╭●─────RX────╭●─┤
+                1: ─╰X───────────╰X─┤
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        cnot = re.ResourceCNOT.resource_rep()
+        rx = re.ResourceRX.resource_rep(eps=eps)
+        return [GateCount(cnot, 2), GateCount(rx)]
+
+    @property
+    def resource_params(self):
+        r"""Returns a dictionary containing the minimal information needed to compute the resources.
+
+        Returns:
+            dict: A dictionary containing the resource parameters:
+            * eps (float): error threshold for clifford plus T decomposition of this operation
+        """
+        return {"eps": self.eps}
+
+    @classmethod
+    def resource_rep(cls, eps=None):
+        """Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Returns:
+            CompressedResourceOp: the operator in a compressed representation
+        """
+        return CompressedResourceOp(cls, {"eps": eps})
+
+    @classmethod
+    def default_adjoint_resource_decomp(cls, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for the adjoint of the operator.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The adjoint of this operator just changes the sign of the phase angle, thus
+            the resources of the adjoint operation results in the original operation.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        return [GateCount(cls.resource_rep(eps=eps))]
+
+    @classmethod
+    def default_controlled_resource_decomp(
+        cls,
+        ctrl_num_ctrl_wires,
+        ctrl_num_ctrl_values,
+        eps=None,
+    ) -> list[GateCount]:
+        r"""Returns a list representing the resources for a controlled version of the operator.
+
+        Args:
+            ctrl_num_ctrl_wires (int): the number of qubits the operation is controlled on
+            ctrl_num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The resources are derived from the following identity. If an operation :math:`\hat{A}`
+            can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}`
+            then the controlled operation :math:`C\hat{A}` can be expressed as:
+
+            .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger}
+
+            Specifically, the resources are one multi-controlled RX-gate and a pair of
+            :class:`~.ResourceCNOT` gates.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+
+        """
+        cnot = re.ResourceCNOT.resource_rep()
+        ctrl_rx = re.ResourceControlled.resource_rep(
+            base_cmpr_op=re.ResourceRX.resource_rep(eps=eps),
+            num_ctrl_wires=ctrl_num_ctrl_wires,
+            num_ctrl_values=ctrl_num_ctrl_values,
+        )
+
+        return [GateCount(cnot, 2), GateCount(ctrl_rx)]
+
+    @classmethod
+    def default_pow_resource_decomp(
+        cls,
+        pow_z,
+        eps=None,
+    ) -> list[GateCount]:
+        r"""Returns a list representing the resources for an operator raised to a power.
+
+        Args:
+            pow_z (int): the power that the operator is being raised to
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            Taking arbitrary powers of a rotation produces a sum of rotations.
+            The resources simplify to just one total Ising rotation.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        return [GateCount(cls.resource_rep(eps=eps))]
+
+
+class ResourceIsingYY(ResourceOperator):
+    r"""Resource class for the IsingYY gate.
+
+    Args:
+        eps (float, optional): error threshold for clifford plus T decomposition of this operation
+        wires (Sequence[int], optional): the wire the operation acts on
+
+    Resources:
+        Ising YY coupling gate
+
+        .. math:: \mathtt{YY}(\phi) = \exp\left(-i \frac{\phi}{2} (Y \otimes Y)\right) =
+            \begin{bmatrix}
+                \cos(\phi / 2) & 0 & 0 & i \sin(\phi / 2) \\
+                0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\
+                0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\
+                i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2)
+            \end{bmatrix}.
+
+        The cost for implementing this transformation is given by:
+
+        .. code-block:: bash
+
+            0: ─╭●─────RY────╭●─┤
+            1: ─╰Y───────────╰Y─┤
+
+    .. seealso:: :class:`~.IsingYY`
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> ising_yy = plre.ResourceIsingYY()
+    >>> gate_set = {"CY", "RY"}
+    >>> print(plre.estimate_resources(ising_yy, gate_set))
+    --- Resources: ---
+    Total qubits: 2
+    Total gates : 3
+    Qubit breakdown:
+     clean qubits: 0, dirty qubits: 0, algorithmic qubits: 2
+    Gate breakdown:
+     {'CY': 2, 'RY': 1}
+
+    """
+
+    num_wires = 2
+    resource_keys = {"eps"}
+
+    def __init__(self, eps=None, wires=None) -> None:
+        self.eps = eps
+        super().__init__(wires=wires)
+
+    @classmethod
+    def default_resource_decomp(cls, eps=None, **kwargs):
+        r"""Returns a list of GateCount objects representing the operator's resources.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            Ising YY coupling gate
+
+            .. math:: \mathtt{YY}(\phi) = \exp\left(-i \frac{\phi}{2} (Y \otimes Y)\right) =
+                \begin{bmatrix}
+                    \cos(\phi / 2) & 0 & 0 & i \sin(\phi / 2) \\
+                    0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\
+                    0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\
+                    i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2)
+                \end{bmatrix}.
+
+            The cost for implementing this transformation is given by:
+
+            .. code-block:: bash
+
+                0: ─╭●─────RY────╭●─┤
+                1: ─╰Y───────────╰Y─┤
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        cy = re.ops.ResourceCY.resource_rep()
+        ry = re.ops.ResourceRY.resource_rep(eps=eps)
+        return [GateCount(cy, 2), GateCount(ry)]
+
+    @property
+    def resource_params(self):
+        r"""Returns a dictionary containing the minimal information needed to compute the resources.
+
+        Returns:
+            dict: A dictionary containing the resource parameters:
+            * eps (float): error threshold for clifford plus T decomposition of this operation
+        """
+        return {"eps": self.eps}
+
+    @classmethod
+    def resource_rep(cls, eps=None):
+        """Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Returns:
+            CompressedResourceOp: the operator in a compressed representation
+        """
+        return CompressedResourceOp(cls, {"eps": eps})
+
+    @classmethod
+    def default_adjoint_resource_decomp(cls, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for the adjoint of the operator.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The adjoint of this operator just changes the sign of the phase angle, thus
+            the resources of the adjoint operation results in the original operation.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        return [GateCount(cls.resource_rep(eps=eps))]
+
+    @classmethod
+    def default_controlled_resource_decomp(
+        cls,
+        ctrl_num_ctrl_wires,
+        ctrl_num_ctrl_values,
+        eps=None,
+    ) -> list[GateCount]:
+        r"""Returns a list representing the resources for a controlled version of the operator.
+
+        Args:
+            ctrl_num_ctrl_wires (int): the number of qubits the operation is controlled on
+            ctrl_num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The resources are derived from the following identity. If an operation :math:`\hat{A}`
+            can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}`
+            then the controlled operation :math:`C\hat{A}` can be expressed as:
+
+            .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger}
+
+            Specifically, the resources are one multi-controlled RY-gate and a pair of
+            :class:`~.ResourceCY` gates.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        cy = re.ops.ResourceCY.resource_rep()
+        ctrl_ry = re.ResourceControlled.resource_rep(
+            base_cmpr_op=re.ResourceRY.resource_rep(eps=eps),
+            num_ctrl_wires=ctrl_num_ctrl_wires,
+            num_ctrl_values=ctrl_num_ctrl_values,
+        )
+
+        return [GateCount(cy, 2), GateCount(ctrl_ry)]
+
+    @classmethod
+    def default_pow_resource_decomp(cls, pow_z, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for an operator raised to a power.
+
+        Args:
+            pow_z (int): the power that the operator is being raised to
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            Taking arbitrary powers of a rotation produces a sum of rotations.
+            The resources simplify to just one total Ising rotation.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        return [GateCount(cls.resource_rep(eps=eps))]
+
+
+class ResourceIsingXY(ResourceOperator):
+    r"""Resource class for the IsingXY gate.
+
+    Args:
+        eps (float, optional): error threshold for Clifford+T decomposition of this operation
+        wires (Sequence[int], optional): the wire the operation acts on
+
+    Resources:
+        Ising (XX + YY) coupling gate
+
+        .. math:: \mathtt{XY}(\phi) = \exp\left(i \frac{\theta}{4} (X \otimes X + Y \otimes Y)\right) =
+            \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & \cos(\phi / 2) & i \sin(\phi / 2) & 0 \\
+                0 & i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\
+                0 & 0 & 0 & 1
+            \end{bmatrix}.
+
+        The cost for implementing this transformation is given by:
+
+        .. code-block:: bash
+
+            0: ──H─╭●─────RY────╭●──H─┤
+            1: ────╰Y─────RX────╰Y────┤
+
+    .. seealso:: :class:`~.IsingXY`
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> ising_xy = plre.ResourceIsingXY()
+    >>> gate_set = {"Hadamard", "CY", "RY", "RX"}
+    >>> print(plre.estimate_resources(ising_xy, gate_set))
+    --- Resources: ---
+    Total qubits: 2
+    Total gates : 6
+    Qubit breakdown:
+     clean qubits: 0, dirty qubits: 0, algorithmic qubits: 2
+    Gate breakdown:
+     {'Hadamard': 2, 'CY': 2, 'RY': 1, 'RX': 1}
+
+    """
+
+    num_wires = 2
+    resource_keys = {"eps"}
+
+    def __init__(self, eps=None, wires=None) -> None:
+        self.eps = eps
+        super().__init__(wires=wires)
+
+    @classmethod
+    def default_resource_decomp(cls, eps=None, **kwargs):
+        r"""Returns a list of GateCount objects representing the operator's resources.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            IsingXY coupling gate
+
+            .. math:: \mathtt{XY}(\phi) = \exp\left(i \frac{\theta}{4} (X \otimes X + Y \otimes Y)\right) =
+                \begin{bmatrix}
+                    1 & 0 & 0 & 0 \\
+                    0 & \cos(\phi / 2) & i \sin(\phi / 2) & 0 \\
+                    0 & i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\
+                    0 & 0 & 0 & 1
+                \end{bmatrix}.
+
+            The cost for implementing this transformation is given by:
+
+            .. code-block:: bash
+
+                0: ──H─╭●─────RY────╭●──H─┤
+                1: ────╰Y─────RX────╰Y────┤
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        h = re.ResourceHadamard.resource_rep()
+        cy = re.ResourceCY.resource_rep()
+        ry = re.ResourceRY.resource_rep(eps=eps)
+        rx = re.ResourceRX.resource_rep(eps=eps)
+
+        return [GateCount(h, 2), GateCount(cy, 2), GateCount(ry), GateCount(rx)]
+
+    @property
+    def resource_params(self):
+        r"""Returns a dictionary containing the minimal information needed to compute the resources.
+
+        Returns:
+            dict: A dictionary containing the resource parameters:
+            * eps (float): error threshold for clifford plus T decomposition of this operation
+        """
+        return {"eps": self.eps}
+
+    @classmethod
+    def resource_rep(cls, eps=None):
+        """Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Returns:
+            CompressedResourceOp: the operator in a compressed representation
+        """
+        return CompressedResourceOp(cls, {"eps": eps})
+
+    @classmethod
+    def default_adjoint_resource_decomp(cls, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for the adjoint of the operator.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The adjoint of this operator just changes the sign of the phase angle, thus
+            the resources of the adjoint operation results in the original operation.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        return [GateCount(cls.resource_rep(eps=eps))]
+
+    @classmethod
+    def default_controlled_resource_decomp(
+        cls,
+        ctrl_num_ctrl_wires,
+        ctrl_num_ctrl_values,
+        eps=None,
+    ) -> list[GateCount]:
+        r"""Returns a list representing the resources for a controlled version of the operator.
+
+        Args:
+            ctrl_num_ctrl_wires (int): the number of qubits the operation is controlled on
+            ctrl_num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The resources are derived from the following identity. If an operation :math:`\hat{A}`
+            can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}`
+            then the controlled operation :math:`C\hat{A}` can be expressed as:
+
+            .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger}
+
+            Specifically, the resources are one multi-controlled RY-gate, one multi-controlled RX-gate,
+            a pair of :class:`~.ResourceCY` gates and a pair of :class:`~.ResourceHadamard` gates.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        h = re.ResourceHadamard.resource_rep()
+        cy = re.ResourceCY.resource_rep()
+        ctrl_rx = re.ResourceControlled.resource_rep(
+            base_cmpr_op=re.ResourceRX.resource_rep(eps=eps),
+            num_ctrl_wires=ctrl_num_ctrl_wires,
+            num_ctrl_values=ctrl_num_ctrl_values,
+        )
+        ctrl_ry = re.ResourceControlled.resource_rep(
+            base_cmpr_op=re.ResourceRY.resource_rep(eps=eps),
+            num_ctrl_wires=ctrl_num_ctrl_wires,
+            num_ctrl_values=ctrl_num_ctrl_values,
+        )
+
+        return [GateCount(h, 2), GateCount(cy, 2), GateCount(ctrl_ry), GateCount(ctrl_rx)]
+
+    @classmethod
+    def default_pow_resource_decomp(cls, pow_z, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for an operator raised to a power.
+
+        Args:
+            pow_z (int): the power that the operator is being raised to
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            Taking arbitrary powers of a rotation produces a sum of rotations.
+            The resources simplify to just one total Ising rotation.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        return [GateCount(cls.resource_rep(eps=eps))]
+
+
+class ResourceIsingZZ(ResourceOperator):
+    r"""Resource class for the IsingZZ gate.
+
+    Args:
+        eps (float, optional): error threshold for Clifford+T decomposition of this operation
+        wires (Sequence[int], optional): the wire the operation acts on
+
+    Resources:
+        Ising ZZ coupling gate
+
+        .. math:: ZZ(\phi) = \exp\left(-i \frac{\phi}{2} (Z \otimes Z)\right) =
+            \begin{bmatrix}
+                e^{-i \phi / 2} & 0 & 0 & 0 \\
+                0 & e^{i \phi / 2} & 0 & 0 \\
+                0 & 0 & e^{i \phi / 2} & 0 \\
+                0 & 0 & 0 & e^{-i \phi / 2}
+            \end{bmatrix}.
+
+        The cost for implmenting this transformation is given by:
+
+        .. code-block:: bash
+
+            0: ─╭●───────────╭●─┤
+            1: ─╰X─────RZ────╰X─┤
+
+    .. seealso:: :class:`~.IsingZZ`
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> ising_zz = plre.ResourceIsingZZ()
+    >>> gate_set = {"CNOT", "RZ"}
+    >>> print(plre.estimate_resources(ising_zz, gate_set))
+    --- Resources: ---
+    Total qubits: 2
+    Total gates : 3
+    Qubit breakdown:
+     clean qubits: 0, dirty qubits: 0, algorithmic qubits: 2
+    Gate breakdown:
+     {'CNOT': 2, 'RZ': 1}
+
+    """
+
+    num_wires = 2
+    resource_keys = {"eps"}
+
+    def __init__(self, eps=None, wires=None) -> None:
+        self.eps = eps
+        super().__init__(wires=wires)
+
+    @classmethod
+    def default_resource_decomp(cls, eps=None, **kwargs):
+        r"""Returns a list of GateCount objects representing the operator's resources.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            Ising ZZ coupling gate
+
+            .. math:: ZZ(\phi) = \exp\left(-i \frac{\phi}{2} (Z \otimes Z)\right) =
+                \begin{bmatrix}
+                    e^{-i \phi / 2} & 0 & 0 & 0 \\
+                    0 & e^{i \phi / 2} & 0 & 0 \\
+                    0 & 0 & e^{i \phi / 2} & 0 \\
+                    0 & 0 & 0 & e^{-i \phi / 2}
+                \end{bmatrix}.
+
+            The cost for implmenting this transformation is given by:
+
+            .. code-block:: bash
+
+                0: ─╭●───────────╭●─┤
+                1: ─╰X─────RZ────╰X─┤
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        cnot = re.ResourceCNOT.resource_rep()
+        rz = re.ResourceRZ.resource_rep(eps=eps)
+
+        gate_types = {}
+        gate_types[cnot] = 2
+        gate_types[rz] = 1
+
+        return [GateCount(cnot, 2), GateCount(rz)]
+
+    @property
+    def resource_params(self):
+        r"""Returns a dictionary containing the minimal information needed to compute the resources.
+
+        Returns:
+            dict: A dictionary containing the resource parameters:
+            * eps (float): error threshold for clifford plus T decomposition of this operation
+        """
+        return {"eps": self.eps}
+
+    @classmethod
+    def resource_rep(cls, eps=None):
+        """Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Returns:
+            CompressedResourceOp: the operator in a compressed representation
+        """
+        return CompressedResourceOp(cls, {"eps": eps})
+
+    @classmethod
+    def default_adjoint_resource_decomp(cls, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for the adjoint of the operator.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The adjoint of this operator just changes the sign of the phase angle, thus
+            the resources of the adjoint operation results in the original operation.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        return [GateCount(cls.resource_rep(eps=eps))]
+
+    @classmethod
+    def default_controlled_resource_decomp(
+        cls,
+        ctrl_num_ctrl_wires,
+        ctrl_num_ctrl_values,
+        eps=None,
+    ) -> list[GateCount]:
+        r"""Returns a list representing the resources for a controlled version of the operator.
+
+        Args:
+            ctrl_num_ctrl_wires (int): the number of qubits the operation is controlled on
+            ctrl_num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The resources are derived from the following identity. If an operation :math:`\hat{A}`
+            can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}`
+            then the controlled operation :math:`C\hat{A}` can be expressed as:
+
+            .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger}
+
+            Specifically, the resources are one multi-controlled RZ-gate and a pair of
+            :class:`~.ResourceCNOT` gates.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        cnot = re.ResourceCNOT.resource_rep()
+        ctrl_rz = re.ResourceControlled.resource_rep(
+            base_cmpr_op=re.ResourceRZ.resource_rep(eps=eps),
+            num_ctrl_wires=ctrl_num_ctrl_wires,
+            num_ctrl_values=ctrl_num_ctrl_values,
+        )
+        return [GateCount(cnot, 2), GateCount(ctrl_rz)]
+
+    @classmethod
+    def default_pow_resource_decomp(cls, pow_z, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for an operator raised to a power.
+
+        Args:
+            pow_z (int): the power that the operator is being raised to
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            Taking arbitrary powers of a rotation produces a sum of rotations.
+            The resources simplify to just one total Ising rotation.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        return [GateCount(cls.resource_rep(eps=eps))]
+
+
+class ResourcePSWAP(ResourceOperator):
+    r"""Resource class for the PSWAP gate.
+
+    Args:
+        eps (float, optional): error threshold for Clifford+T decomposition of this operation
+        wires (Sequence[int], optional): the wire the operation acts on
+
+    Resources:
+        The :code:`PSWAP` gate is defined as:
+
+        .. math:: PSWAP(\phi) = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 0 & e^{i \phi} & 0 \\
+                0 & e^{i \phi} & 0 & 0 \\
+                0 & 0 & 0 & 1
+            \end{bmatrix}.
+
+        The cost for implementing this transformation is given by:
+
+        .. code-block:: bash
+
+            0: ─╭SWAP─╭●───────────╭●─┤
+            1: ─╰SWAP─╰X─────Rϕ────╰X─┤
+
+    .. seealso:: :class:`~.PSWAP`
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> pswap = plre.ResourcePSWAP()
+    >>> gate_set = {"CNOT", "SWAP", "PhaseShift"}
+    >>> print(plre.estimate_resources(pswap, gate_set))
+    --- Resources: ---
+    Total qubits: 2
+    Total gates : 4
+    Qubit breakdown:
+     clean qubits: 0, dirty qubits: 0, algorithmic qubits: 2
+    Gate breakdown:
+     {'SWAP': 1, 'PhaseShift': 1, 'CNOT': 2}
+
+    """
+
+    num_wires = 2
+    resource_keys = {"eps"}
+
+    def __init__(self, eps=None, wires=None) -> None:
+        self.eps = eps
+        super().__init__(wires=wires)
+
+    @classmethod
+    def default_resource_decomp(cls, eps=None, **kwargs):
+        r"""Returns a list of GateCount objects representing the operator's resources.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The :code:`PSWAP` gate is defined as:
+
+            .. math:: PSWAP(\phi) = \begin{bmatrix}
+                    1 & 0 & 0 & 0 \\
+                    0 & 0 & e^{i \phi} & 0 \\
+                    0 & e^{i \phi} & 0 & 0 \\
+                    0 & 0 & 0 & 1
+                \end{bmatrix}.
+
+            The cost for implementing this transformation is given by:
+
+            .. code-block:: bash
+
+                0: ─╭SWAP─╭●───────────╭●─┤
+                1: ─╰SWAP─╰X─────Rϕ────╰X─┤
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        swap = re.ResourceSWAP.resource_rep()
+        cnot = re.ResourceCNOT.resource_rep()
+        phase = re.ResourcePhaseShift.resource_rep(eps=eps)
+
+        return [GateCount(swap), GateCount(phase), GateCount(cnot, 2)]
+
+    @property
+    def resource_params(self):
+        r"""Returns a dictionary containing the minimal information needed to compute the resources.
+
+        Returns:
+            dict: A dictionary containing the resource parameters:
+            * eps (float): error threshold for clifford plus T decomposition of this operation
+        """
+        return {"eps": self.eps}
+
+    @classmethod
+    def resource_rep(cls, eps=None):
+        """Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Returns:
+            CompressedResourceOp: the operator in a compressed representation
+        """
+        return CompressedResourceOp(cls, {"eps": eps})
+
+    @classmethod
+    def default_adjoint_resource_decomp(cls, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for the adjoint of the operator.
+
+        Args:
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+            Resources:
+                The adjoint of this operator just changes the sign of the phase angle, thus
+                the resources of the adjoint operation results in the original operation.
+
+            Returns:
+                list[GateCount]: A list of GateCount objects, where each object
+                represents a specific quantum gate and the number of times it appears
+                in the decomposition.
+
+        """
+        return [GateCount(cls.resource_rep(eps=eps))]
+
+    @classmethod
+    def default_controlled_resource_decomp(
+        cls,
+        ctrl_num_ctrl_wires,
+        ctrl_num_ctrl_values,
+        eps=None,
+    ) -> list[GateCount]:
+        r"""Returns a list representing the resources for a controlled version of the operator.
+
+        Args:
+            ctrl_num_ctrl_wires (int): the number of qubits the operation is controlled on
+            ctrl_num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state
+            eps (float, optional): error threshold for clifford plus T decomposition of this operation
+
+        Resources:
+            The resources are derived from the following identity. If an operation :math:`\hat{A}`
+            can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}`
+            then the controlled operation :math:`C\hat{A}` can be expressed as:
+
+            .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger}
+
+            Specifically, the resources are one multi-controlled phase shift gate, one multi-controlled
+            SWAP gate and a pair of :class:`~.ResourceCNOT` gates.
+
+        Returns:
+            list[GateCount]: A list of GateCount objects, where each object
+            represents a specific quantum gate and the number of times it appears
+            in the decomposition.
+        """
+        cnot = re.ResourceCNOT.resource_rep()
+        ctrl_swap = re.ResourceControlled.resource_rep(
+            base_cmpr_op=re.ResourceSWAP.resource_rep(),
+            num_ctrl_wires=ctrl_num_ctrl_wires,
+            num_ctrl_values=ctrl_num_ctrl_values,
+        )
+        ctrl_ps = re.ResourceControlled.resource_rep(
+            base_cmpr_op=re.ResourcePhaseShift.resource_rep(eps=eps),
+            num_ctrl_wires=ctrl_num_ctrl_wires,
+            num_ctrl_values=ctrl_num_ctrl_values,
+        )
+
+        return [GateCount(ctrl_swap), GateCount(cnot, 2), GateCount(ctrl_ps)]


Test patch:
diff --git a/pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_multi_qubit.py b/pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_multi_qubit.py
new file mode 100644
index 000000000..ec66fd128
--- /dev/null
+++ b/pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_multi_qubit.py
@@ -0,0 +1,686 @@
+# Copyright 2025 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.
+"""Tests for parametric multi qubit resource operators."""
+
+import pytest
+
+import pennylane.labs.resource_estimation as re
+
+# pylint: disable=use-implicit-booleaness-not-comparison,no-self-use,too-many-arguments
+
+
+class TestMultiRZ:
+    """Test the ResourceMultiRZ class."""
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    @pytest.mark.parametrize("num_wires", range(1, 5))
+    def test_resource_params(self, num_wires, eps):
+        """Test that the resource params are correct."""
+        if eps:
+            op = re.ResourceMultiRZ(num_wires, eps=eps)
+        else:
+            op = re.ResourceMultiRZ(num_wires)
+
+        assert op.resource_params == {"num_wires": num_wires, "eps": eps}
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    @pytest.mark.parametrize("num_wires", range(1, 5))
+    def test_resource_rep(self, num_wires, eps):
+        """Test that the compressed representation is correct."""
+        expected = re.CompressedResourceOp(re.ResourceMultiRZ, {"num_wires": num_wires, "eps": eps})
+        assert re.ResourceMultiRZ.resource_rep(num_wires, eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    @pytest.mark.parametrize("num_wires", range(1, 5))
+    def test_resources(self, num_wires, eps):
+        """Test that the resources are correct."""
+        expected = [
+            re.GateCount(re.ResourceCNOT.resource_rep(), 2 * (num_wires - 1)),
+            re.GateCount(re.ResourceRZ.resource_rep(eps=eps)),
+        ]
+        assert re.ResourceMultiRZ.resource_decomp(num_wires, eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    @pytest.mark.parametrize("num_wires", range(1, 5))
+    def test_resources_from_rep(self, num_wires, eps):
+        """Test that the resources can be computed from the compressed representation and params."""
+        op = re.ResourceMultiRZ(num_wires, eps)
+        expected = [
+            re.GateCount(re.ResourceCNOT.resource_rep(), 2 * (num_wires - 1)),
+            re.GateCount(re.ResourceRZ.resource_rep(eps=eps)),
+        ]
+
+        op_compressed_rep = op.resource_rep_from_op()
+        op_resource_type = op_compressed_rep.op_type
+        op_resource_params = op_compressed_rep.params
+        assert op_resource_type.resource_decomp(**op_resource_params) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    @pytest.mark.parametrize("num_wires", range(1, 5))
+    def test_adjoint_decomp(self, num_wires, eps):
+        """Test that the adjoint decomposition is correct."""
+        expected = [re.GateCount(re.ResourceMultiRZ.resource_rep(num_wires=num_wires, eps=eps))]
+        assert re.ResourceMultiRZ.adjoint_resource_decomp(num_wires=num_wires, eps=eps) == expected
+
+    ctrl_data = (
+        (
+            1,
+            0,
+            [
+                re.GateCount(re.resource_rep(re.ResourceCNOT()), 4),
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceRZ.resource_rep(1e-3), 1, 0)
+                ),
+            ],
+        ),
+        (
+            1,
+            1,
+            [
+                re.GateCount(re.resource_rep(re.ResourceCNOT()), 4),
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceRZ.resource_rep(1e-3), 1, 1)
+                ),
+            ],
+        ),
+        (
+            2,
+            0,
+            [
+                re.GateCount(re.resource_rep(re.ResourceCNOT()), 4),
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceRZ.resource_rep(1e-3), 2, 0)
+                ),
+            ],
+        ),
+        (
+            3,
+            2,
+            [
+                re.GateCount(re.resource_rep(re.ResourceCNOT()), 4),
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceRZ.resource_rep(1e-3), 3, 2)
+                ),
+            ],
+        ),
+    )
+
+    @pytest.mark.parametrize("num_ctrl_wires, num_ctrl_values, expected_res", ctrl_data)
+    def test_resource_controlled(self, num_ctrl_wires, num_ctrl_values, expected_res):
+        """Test that the controlled resources are as expected"""
+
+        op = re.ResourceMultiRZ(num_wires=3, eps=1e-3)
+        op2 = re.ResourceControlled(
+            op,
+            num_ctrl_wires,
+            num_ctrl_values,
+        )
+
+        assert (
+            op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, **op.resource_params)
+            == expected_res
+        )
+        assert op2.resource_decomp(**op2.resource_params) == expected_res
+
+    @pytest.mark.parametrize("z", range(1, 5))
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    @pytest.mark.parametrize("num_wires", range(1, 5))
+    def test_pow_decomp(self, z, num_wires, eps):
+        """Test that the pow decomposition is correct."""
+        op = re.ResourceMultiRZ(num_wires, eps=eps)
+        expected_res = [re.GateCount(re.ResourceMultiRZ.resource_rep(num_wires, eps))]
+        assert op.pow_resource_decomp(z, **op.resource_params) == expected_res
+
+
+class TestPauliRot:
+    """Test the ResourcePauliRot class."""
+
+    pauli_words = ("I", "XYZ", "XXX", "XIYIZIX", "III")
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    @pytest.mark.parametrize("pauli_string", pauli_words)
+    def test_resource_params(self, pauli_string, eps):
+        """Test that the resource params are correct."""
+        op = re.ResourcePauliRot(pauli_string=pauli_string, eps=eps)
+        assert op.resource_params == {"pauli_string": pauli_string, "eps": eps}
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    @pytest.mark.parametrize("pauli_string", pauli_words)
+    def test_resource_rep(self, pauli_string, eps):
+        """Test that the compressed representation is correct."""
+        expected = re.CompressedResourceOp(
+            re.ResourcePauliRot, {"pauli_string": pauli_string, "eps": eps}
+        )
+        assert re.ResourcePauliRot.resource_rep(pauli_string, eps) == expected
+
+    expected_h_count = (0, 4, 6, 6, 0)
+    expected_s_count = (0, 1, 0, 1, 0)
+    params = zip(pauli_words, expected_h_count, expected_s_count)
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    @pytest.mark.parametrize("pauli_string, expected_h_count, expected_s_count", params)
+    def test_resources(self, pauli_string, expected_h_count, expected_s_count, eps):
+        """Test that the resources are correct."""
+        active_wires = len(pauli_string.replace("I", ""))
+
+        if set(pauli_string) == {"I"}:
+            expected = [re.GateCount(re.ResourceGlobalPhase.resource_rep())]
+        else:
+            expected = []
+
+            if expected_h_count:
+                expected.append(re.GateCount(re.ResourceHadamard.resource_rep(), expected_h_count))
+
+            if expected_s_count:
+                expected.append(re.GateCount(re.ResourceS.resource_rep(), expected_s_count))
+                expected.append(
+                    re.GateCount(
+                        re.ResourceAdjoint.resource_rep(re.ResourceS.resource_rep()),
+                        expected_s_count,
+                    )
+                )
+
+            expected.append(re.GateCount(re.ResourceRZ.resource_rep(eps=eps)))
+            expected.append(re.GateCount(re.ResourceCNOT.resource_rep(), 2 * (active_wires - 1)))
+
+        assert re.ResourcePauliRot.resource_decomp(pauli_string, eps=eps) == expected
+
+    def test_resources_empty_pauli_string(self):
+        """Test that the resources method produces the correct result for an empty pauli string."""
+        expected = [re.GateCount(re.ResourceGlobalPhase.resource_rep())]
+        assert re.ResourcePauliRot.resource_decomp(pauli_string="") == expected
+
+    @pytest.mark.parametrize("pauli_string, expected_h_count, expected_s_count", params)
+    def test_resources_from_rep(self, pauli_string, expected_h_count, expected_s_count):
+        """Test that the resources can be computed from the compressed representation and params."""
+        op = re.ResourcePauliRot(0.5, pauli_string, wires=range(len(pauli_string)))
+        active_wires = len(pauli_string.replace("I", ""))
+
+        if set(pauli_string) == {"I"}:
+            expected = [re.GateCount(re.ResourceGlobalPhase.resource_rep())]
+        else:
+            expected = [
+                re.GateCount(re.ResourceRZ.resource_rep()),
+                re.GateCount(re.ResourceCNOT.resource_rep(), 2 * (active_wires - 1)),
+            ]
+
+            if expected_h_count:
+                expected.append(re.GateCount(re.ResourceHadamard.resource_rep(), expected_h_count))
+
+            if expected_s_count:
+                expected.append(re.GateCount(re.ResourceS.resource_rep(), expected_s_count))
+                expected.append(
+                    re.GateCount(
+                        re.ResourceAdjoint.resource_rep(re.ResourceS.resource_rep()),
+                        expected_s_count,
+                    )
+                )
+
+        op_compressed_rep = op.resource_rep_from_op()
+        op_resource_type = op_compressed_rep.op_type
+        op_resource_params = op_compressed_rep.params
+        assert op_resource_type.resource_decomp(**op_resource_params) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    @pytest.mark.parametrize("pauli_word", pauli_words)
+    def test_adjoint_decomp(self, pauli_word, eps):
+        """Test that the adjoint decomposition is correct."""
+        expected = [
+            re.GateCount(re.ResourcePauliRot.resource_rep(pauli_string=pauli_word, eps=eps))
+        ]
+        assert (
+            re.ResourcePauliRot.adjoint_resource_decomp(pauli_string=pauli_word, eps=eps)
+            == expected
+        )
+
+    ctrl_data = (
+        (
+            "XXX",
+            1,
+            0,
+            [
+                re.GateCount(re.ResourceHadamard.resource_rep(), 6),
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceRZ.resource_rep(eps=1e-5), 1, 0),
+                    1,
+                ),
+                re.GateCount(re.ResourceCNOT.resource_rep(), 4),
+            ],
+        ),
+        (
+            "XXX",
+            1,
+            1,
+            [
+                re.GateCount(re.ResourceHadamard.resource_rep(), 6),
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceRZ.resource_rep(eps=1e-5), 1, 1),
+                    1,
+                ),
+                re.GateCount(re.ResourceCNOT.resource_rep(), 4),
+            ],
+        ),
+        (
+            "XXX",
+            2,
+            0,
+            [
+                re.GateCount(re.ResourceHadamard.resource_rep(), 6),
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceRZ.resource_rep(eps=1e-5), 2, 0),
+                    1,
+                ),
+                re.GateCount(re.ResourceCNOT.resource_rep(), 4),
+            ],
+        ),
+        (
+            "XIYIZIX",
+            1,
+            0,
+            [
+                re.GateCount(re.ResourceHadamard.resource_rep(), 6),
+                re.GateCount(re.ResourceS.resource_rep(), 1),
+                re.GateCount(re.ResourceAdjoint.resource_rep(re.ResourceS.resource_rep()), 1),
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceRZ.resource_rep(eps=1e-5), 1, 0),
+                    1,
+                ),
+                re.GateCount(re.ResourceCNOT.resource_rep(), 6),
+            ],
+        ),
+        (
+            "XIYIZIX",
+            1,
+            1,
+            [
+                re.GateCount(re.ResourceHadamard.resource_rep(), 6),
+                re.GateCount(re.ResourceS.resource_rep(), 1),
+                re.GateCount(re.ResourceAdjoint.resource_rep(re.ResourceS.resource_rep()), 1),
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceRZ.resource_rep(eps=1e-5), 1, 1),
+                    1,
+                ),
+                re.GateCount(re.ResourceCNOT.resource_rep(), 6),
+            ],
+        ),
+        (
+            "XIYIZIX",
+            2,
+            0,
+            [
+                re.GateCount(re.ResourceHadamard.resource_rep(), 6),
+                re.GateCount(re.ResourceS.resource_rep(), 1),
+                re.GateCount(re.ResourceAdjoint.resource_rep(re.ResourceS.resource_rep()), 1),
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceRZ.resource_rep(eps=1e-5), 2, 0),
+                    1,
+                ),
+                re.GateCount(re.ResourceCNOT.resource_rep(), 6),
+            ],
+        ),
+        (
+            "III",
+            1,
+            0,
+            [
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceGlobalPhase.resource_rep(), 1, 0)
+                )
+            ],
+        ),
+        (
+            "X",
+            1,
+            1,
+            [
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceRX.resource_rep(eps=1e-5), 1, 1)
+                )
+            ],
+        ),
+        (
+            "Y",
+            2,
+            0,
+            [
+                re.GateCount(
+                    re.ResourceControlled.resource_rep(re.ResourceRY.resource_rep(eps=1e-5), 2, 0)
+                )
+            ],
+        ),
+    )
+
+    @pytest.mark.parametrize("pauli_word, num_ctrl_wires, num_ctrl_values, expected_res", ctrl_data)
+    def test_resource_controlled(self, num_ctrl_wires, num_ctrl_values, pauli_word, expected_res):
+        """Test that the controlled resources are as expected"""
+
+        op = re.ResourcePauliRot(pauli_word, eps=1e-5)
+        op2 = re.ResourceControlled(op, num_ctrl_wires, num_ctrl_values)
+
+        assert (
+            op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, **op.resource_params)
+            == expected_res
+        )
+        assert op2.resource_decomp(**op2.resource_params) == expected_res
+
+    @pytest.mark.parametrize("z", range(1, 5))
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    @pytest.mark.parametrize("pauli_word", pauli_words)
+    def test_pow_decomp(self, z, pauli_word, eps):
+        """Test that the pow decomposition is correct."""
+        op = re.ResourcePauliRot(pauli_string=pauli_word, eps=eps)
+        expected_res = [
+            re.GateCount(re.ResourcePauliRot.resource_rep(pauli_string=pauli_word, eps=eps))
+        ]
+        assert op.pow_resource_decomp(z, **op.resource_params) == expected_res
+
+        op2 = re.ResourcePow(op, z)
+        assert op2.resource_decomp(**op2.resource_params) == expected_res
+
+
+class TestIsingXX:
+    """Test the IsingXX class."""
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resource_params(self, eps):
+        """Test that the resource params are correct."""
+        if eps:
+            op = re.ResourceIsingXX(eps=eps)
+        else:
+            op = re.ResourceIsingXX()
+
+        assert op.resource_params == {"eps": eps}
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resource_rep(self, eps):
+        """Test that the compressed representation is correct."""
+        expected = re.CompressedResourceOp(re.ResourceIsingXX, {"eps": eps})
+        assert re.ResourceIsingXX.resource_rep(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources(self, eps):
+        """Test that the resources are correct."""
+        expected = [
+            re.GateCount(re.ResourceCNOT.resource_rep(), 2),
+            re.GateCount(re.ResourceRX.resource_rep(eps=eps)),
+        ]
+        assert re.ResourceIsingXX.resource_decomp(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_pow(self, eps):
+        """Test that the pow resources are correct."""
+        expected = [re.GateCount(re.ResourceIsingXX.resource_rep(eps=eps))]
+        assert re.ResourceIsingXX.pow_resource_decomp(pow_z=3, eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_adjoint(self, eps):
+        """Test that the adjoint resources are correct."""
+        expected = [re.GateCount(re.ResourceIsingXX.resource_rep(eps=eps))]
+        assert re.ResourceIsingXX.adjoint_resource_decomp(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_controlled(self, eps):
+        """Test that the controlled resources are correct."""
+        expected = [
+            re.GateCount(re.ResourceCNOT.resource_rep(), 2),
+            re.GateCount(
+                re.ResourceControlled.resource_rep(
+                    re.ResourceRX.resource_rep(eps=eps),
+                    3,
+                    2,
+                )
+            ),
+        ]
+        op = re.ResourceControlled(re.ResourceIsingXX(eps=eps), num_ctrl_wires=3, num_ctrl_values=2)
+        assert op.resource_decomp(**op.resource_params) == expected
+
+
+class TestIsingXY:
+    """Test the IsingXY class."""
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resource_params(self, eps):
+        """Test that the resource params are correct."""
+        if eps:
+            op = re.ResourceIsingXY(eps=eps)
+        else:
+            op = re.ResourceIsingXY()
+
+        assert op.resource_params == {"eps": eps}
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resource_rep(self, eps):
+        """Test that the compressed representation is correct."""
+        expected = re.CompressedResourceOp(re.ResourceIsingXY, {"eps": eps})
+        assert re.ResourceIsingXY.resource_rep(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources(self, eps):
+        """Test that the resources are correct."""
+        expected = [
+            re.GateCount(re.ResourceHadamard.resource_rep(), 2),
+            re.GateCount(re.ResourceCY.resource_rep(), 2),
+            re.GateCount(re.ResourceRY.resource_rep(eps=eps)),
+            re.GateCount(re.ResourceRX.resource_rep(eps=eps)),
+        ]
+        assert re.ResourceIsingXY.resource_decomp(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_pow(self, eps):
+        """Test that the pow resources are correct."""
+        expected = [re.GateCount(re.ResourceIsingXY.resource_rep(eps=eps))]
+        assert re.ResourceIsingXY.pow_resource_decomp(pow_z=3, eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_adjoint(self, eps):
+        """Test that the adjoint resources are correct."""
+        expected = [re.GateCount(re.ResourceIsingXY.resource_rep(eps=eps))]
+        assert re.ResourceIsingXY.adjoint_resource_decomp(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_controlled(self, eps):
+        """Test that the controlled resources are correct."""
+        expected = [
+            re.GateCount(re.ResourceHadamard.resource_rep(), 2),
+            re.GateCount(re.ResourceCY.resource_rep(), 2),
+            re.GateCount(
+                re.ResourceControlled.resource_rep(
+                    re.ResourceRY.resource_rep(eps=eps),
+                    3,
+                    2,
+                )
+            ),
+            re.GateCount(
+                re.ResourceControlled.resource_rep(
+                    re.ResourceRX.resource_rep(eps=eps),
+                    3,
+                    2,
+                )
+            ),
+        ]
+        op = re.ResourceControlled(re.ResourceIsingXY(eps=eps), num_ctrl_wires=3, num_ctrl_values=2)
+        assert op.resource_decomp(**op.resource_params) == expected
+
+
+class TestIsingYY:
+    """Test the IsingYY class."""
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resource_params(self, eps):
+        """Test that the resource params are correct."""
+        if eps:
+            op = re.ResourceIsingYY(eps=eps)
+        else:
+            op = re.ResourceIsingYY()
+
+        assert op.resource_params == {"eps": eps}
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resource_rep(self, eps):
+        """Test that the compressed representation is correct."""
+        expected = re.CompressedResourceOp(re.ResourceIsingYY, {"eps": eps})
+        assert re.ResourceIsingYY.resource_rep(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources(self, eps):
+        """Test that the resources are correct."""
+        expected = [
+            re.GateCount(re.ResourceCY.resource_rep(), 2),
+            re.GateCount(re.ResourceRY.resource_rep(eps=eps)),
+        ]
+        assert re.ResourceIsingYY.resource_decomp(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_pow(self, eps):
+        """Test that the pow resources are correct."""
+        expected = [re.GateCount(re.ResourceIsingYY.resource_rep(eps=eps))]
+        assert re.ResourceIsingYY.pow_resource_decomp(pow_z=3, eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_adjoint(self, eps):
+        """Test that the adjoint resources are correct."""
+        expected = [re.GateCount(re.ResourceIsingYY.resource_rep(eps=eps))]
+        assert re.ResourceIsingYY.adjoint_resource_decomp(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_controlled(self, eps):
+        """Test that the controlled resources are correct."""
+        expected = [
+            re.GateCount(re.ResourceCY.resource_rep(), 2),
+            re.GateCount(
+                re.ResourceControlled.resource_rep(
+                    re.ResourceRY.resource_rep(eps=eps),
+                    3,
+                    2,
+                )
+            ),
+        ]
+        op = re.ResourceControlled(re.ResourceIsingYY(eps=eps), num_ctrl_wires=3, num_ctrl_values=2)
+        assert op.resource_decomp(**op.resource_params) == expected
+
+
+class TestIsingZZ:
+    """Test the IsingZZ class."""
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resource_params(self, eps):
+        """Test that the resource params are correct."""
+        if eps:
+            op = re.ResourceIsingZZ(eps=eps)
+        else:
+            op = re.ResourceIsingZZ()
+
+        assert op.resource_params == {"eps": eps}
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resource_rep(self, eps):
+        """Test that the compressed representation is correct."""
+        expected = re.CompressedResourceOp(re.ResourceIsingZZ, {"eps": eps})
+        assert re.ResourceIsingZZ.resource_rep(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources(self, eps):
+        """Test that the resources are correct."""
+        expected = [
+            re.GateCount(re.ResourceCNOT.resource_rep(), 2),
+            re.GateCount(re.ResourceRZ.resource_rep(eps=eps)),
+        ]
+        assert re.ResourceIsingZZ.resource_decomp(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_pow(self, eps):
+        """Test that the pow resources are correct."""
+        expected = [re.GateCount(re.ResourceIsingZZ.resource_rep(eps=eps))]
+        assert re.ResourceIsingZZ.pow_resource_decomp(pow_z=3, eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_adjoint(self, eps):
+        """Test that the adjoint resources are correct."""
+        expected = [re.GateCount(re.ResourceIsingZZ.resource_rep(eps=eps))]
+        assert re.ResourceIsingZZ.adjoint_resource_decomp(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_controlled(self, eps):
+        """Test that the controlled resources are correct."""
+        expected = [
+            re.GateCount(re.ResourceCNOT.resource_rep(), 2),
+            re.GateCount(
+                re.ResourceControlled.resource_rep(
+                    re.ResourceRZ.resource_rep(eps=eps),
+                    3,
+                    2,
+                )
+            ),
+        ]
+        op = re.ResourceControlled(re.ResourceIsingZZ(eps=eps), num_ctrl_wires=3, num_ctrl_values=2)
+        assert op.resource_decomp(**op.resource_params) == expected
+
+
+class TestPSWAP:
+    """Test the PSWAP class."""
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resource_params(self, eps):
+        """Test that the resource params are correct."""
+        if eps:
+            op = re.ResourcePSWAP(eps=eps)
+        else:
+            op = re.ResourcePSWAP()
+
+        assert op.resource_params == {"eps": eps}
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resource_rep(self, eps):
+        """Test that the compressed representation is correct."""
+        expected = re.CompressedResourceOp(re.ResourcePSWAP, {"eps": eps})
+        assert re.ResourcePSWAP.resource_rep(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources(self, eps):
+        """Test that the resources are correct."""
+        expected = [
+            re.GateCount(re.ResourceSWAP.resource_rep()),
+            re.GateCount(re.ResourcePhaseShift.resource_rep(eps=eps)),
+            re.GateCount(re.ResourceCNOT.resource_rep(), 2),
+        ]
+        assert re.ResourcePSWAP.resource_decomp(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_adjoint(self, eps):
+        """Test that the adjoint resources are correct."""
+        expected = [re.GateCount(re.ResourcePSWAP.resource_rep(eps=eps))]
+        assert re.ResourcePSWAP.adjoint_resource_decomp(eps=eps) == expected
+
+    @pytest.mark.parametrize("eps", (None, 1e-3))
+    def test_resources_controlled(self, eps):
+        """Test that the controlled resources are correct."""
+        expected = [
+            re.GateCount(
+                re.ResourceControlled.resource_rep(
+                    re.ResourceSWAP.resource_rep(),
+                    3,
+                    2,
+                )
+            ),
+            re.GateCount(re.ResourceCNOT.resource_rep(), 2),
+            re.GateCount(
+                re.ResourceControlled.resource_rep(
+                    re.ResourcePhaseShift.resource_rep(eps=eps),
+                    3,
+                    2,
+                )
+            ),
+        ]
+        op = re.ResourceControlled(re.ResourcePSWAP(eps=eps), num_ctrl_wires=3, num_ctrl_values=2)
+        assert op.resource_decomp(**op.resource_params) == expected