Repository: PennyLaneAI/pennylane
Pull request #7786 (2025-07-04 14:39:34)

Add `ResourceOperator` support for various Matrix Ops with optimal resources
**Context:**
Adding and upgrading the resources of key templates with state of the art resource estimates.

**Description of the Change:**
- Added resources for the SelectPauliRot template and the QubitUnitary templates
- Fixed a bug where `resource_keys` weren't properly set for controlled single qubit rotation operators




Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 6d909d7b0..25e46a2f4 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -6,6 +6,12 @@
 
 <h3>Improvements 🛠</h3>
 
+<h3>Labs: a place for unified and rapid prototyping of research software 🧪</h3>
+
+* Added state of the art resources for the `ResourceSelectPauliRot` template and the
+  `ResourceQubitUnitary` templates.
+  [(#7786)](https://github.com/PennyLaneAI/pennylane/pull/7786)
+
 <h3>Breaking changes 💔</h3>
 
 <h3>Deprecations 👋</h3>
@@ -25,3 +31,4 @@
 This release contains contributions from (in alphabetical order):
 
 Andrija Paurevic,
+Jay Soni,
diff --git a/pennylane/labs/resource_estimation/__init__.py b/pennylane/labs/resource_estimation/__init__.py
index 8a702c6be..55bdef8ab 100644
--- a/pennylane/labs/resource_estimation/__init__.py
+++ b/pennylane/labs/resource_estimation/__init__.py
@@ -125,6 +125,8 @@ Templates:
     ~ResourceBasisRotation
     ~ResourceSelect
     ~ResourceQROM
+    ~ResourceSelectPauliRot
+    ~ResourceQubitUnitary
     ~ResourceTrotterCDF
     ~ResourceTrotterTHC
     ~ResourceTrotterVibrational
@@ -212,6 +214,8 @@ from .templates import (
     ResourceBasisRotation,
     ResourceSelect,
     ResourceQROM,
+    ResourceQubitUnitary,
+    ResourceSelectPauliRot,
     ResourceTrotterCDF,
     ResourceTrotterTHC,
     CompactHamiltonian,
diff --git a/pennylane/labs/resource_estimation/ops/op_math/controlled_ops.py b/pennylane/labs/resource_estimation/ops/op_math/controlled_ops.py
index df463080d..d8f07c299 100644
--- a/pennylane/labs/resource_estimation/ops/op_math/controlled_ops.py
+++ b/pennylane/labs/resource_estimation/ops/op_math/controlled_ops.py
@@ -1422,6 +1422,7 @@ class ResourceCRX(ResourceOperator):
     [(2 x CNOT), (2 x RZ), (2 x Hadamard)]
     """
 
+    resource_keys = {"eps"}
     num_wires = 2
 
     def __init__(self, eps=None, wires=None) -> None:
@@ -1571,6 +1572,7 @@ class ResourceCRY(ResourceOperator):
     [(2 x CNOT), (2 x RY)]
     """
 
+    resource_keys = {"eps"}
     num_wires = 2
 
     def __init__(self, eps=None, wires=None) -> None:
@@ -1718,6 +1720,7 @@ class ResourceCRZ(ResourceOperator):
     [(2 x CNOT), (2 x RZ)]
     """
 
+    resource_keys = {"eps"}
     num_wires = 2
 
     def __init__(self, eps=None, wires=None) -> None:
@@ -1873,6 +1876,7 @@ class ResourceCRot(ResourceOperator):
     [(2 x CNOT), (3 x RZ), (2 x RY)]
     """
 
+    resource_keys = {"eps"}
     num_wires = 2
 
     def __init__(self, eps=None, wires=None) -> None:
@@ -2031,6 +2035,7 @@ class ResourceControlledPhaseShift(ResourceOperator):
     [(2 x CNOT), (3 x RZ)]
     """
 
+    resource_keys = {"eps"}
     num_wires = 2
 
     def __init__(self, eps=None, wires=None) -> None:
diff --git a/pennylane/labs/resource_estimation/resource_tracking.py b/pennylane/labs/resource_estimation/resource_tracking.py
index bb6c6e10f..65bea08c4 100644
--- a/pennylane/labs/resource_estimation/resource_tracking.py
+++ b/pennylane/labs/resource_estimation/resource_tracking.py
@@ -68,7 +68,8 @@ resource_config = {
     "error_rx": 1e-9,
     "error_ry": 1e-9,
     "error_rz": 1e-9,
-    "precision_multiplexer": 1e-9,
+    "precision_select_pauli_rot": 1e-9,
+    "precision_qubit_unitary": 1e-9,
     "precision_qrom_state_prep": 1e-9,
 }
 
diff --git a/pennylane/labs/resource_estimation/templates/__init__.py b/pennylane/labs/resource_estimation/templates/__init__.py
index 48309074f..4ef2b5794 100644
--- a/pennylane/labs/resource_estimation/templates/__init__.py
+++ b/pennylane/labs/resource_estimation/templates/__init__.py
@@ -21,6 +21,8 @@ from .subroutines import (
     ResourceBasisRotation,
     ResourceSelect,
     ResourceQROM,
+    ResourceSelectPauliRot,
+    ResourceQubitUnitary,
 )
 from .trotter import (
     ResourceTrotterCDF,
diff --git a/pennylane/labs/resource_estimation/templates/subroutines.py b/pennylane/labs/resource_estimation/templates/subroutines.py
index 8628fadca..888076129 100644
--- a/pennylane/labs/resource_estimation/templates/subroutines.py
+++ b/pennylane/labs/resource_estimation/templates/subroutines.py
@@ -1131,3 +1131,350 @@ class ResourceQROM(ResourceOperator):
             "clean": clean,
         }
         return CompressedResourceOp(cls, params)
+
+
+class ResourceQubitUnitary(ResourceOperator):
+    r"""Resource class for the QubitUnitary template.
+
+    Args:
+        num_wires (int): the number of qubits the operation acts upon
+        precision (Union[float, None], optional): The precision used when preparing the single qubit
+            rotations used to synthesize the n-qubit unitary.
+        wires (Sequence[int], optional): the wires the operation acts on
+
+    Resources:
+        The resources are defined by combining the two equalities in `Möttönen and Vartiainen
+        (2005), Fig 14 <https://arxiv.org/pdf/quant-ph/0504100>`_ , we can express an :math:`n`
+        qubit unitary as four :math:`n - 1` qubit unitaries and three multiplexed rotations
+        via ( :class:`~.labs.resource_estimation.ResourceSelectPauliRot` ). Specifically, the cost
+        is given by:
+
+        * 1-qubit unitary, the cost is approximated as a single :code:`RZ` rotation.
+
+        * 2-qubit unitary, the cost is approximated as four single qubit rotations and three :code:`CNOT` gates.
+
+        * 3-qubit unitary or more, the cost is given according to the reference above, recursively.
+
+    .. seealso:: :class:`~.QubitUnitary`
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> qu = plre.ResourceQubitUnitary(num_wires=3)
+    >>> print(plre.estimate_resources(qu, gate_set))
+    --- Resources: ---
+     Total qubits: 3
+     Total gates : 52
+     Qubit breakdown:
+      clean qubits: 0, dirty qubits: 0, algorithmic qubits: 3
+     Gate breakdown:
+      {'RZ': 24, 'CNOT': 24, 'RY': 4}
+    """
+
+    resource_keys = {"num_wires", "precision"}
+
+    def __init__(self, num_wires, precision=None, wires=None):
+        self.num_wires = num_wires
+        self.precision = precision
+        super().__init__(wires=wires)
+
+    @property
+    def resource_params(self) -> dict:
+        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
+                * precision (Union[float, None], optional): The precision used when preparing the
+                  single qubit rotations used to synthesize the n-qubit unitary.
+        """
+        return {"num_wires": self.num_wires, "precision": self.precision}
+
+    @classmethod
+    def resource_rep(cls, num_wires, precision) -> CompressedResourceOp:
+        r"""Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute the resources.
+
+        Args:
+            num_wires (int): the number of qubits the operation acts upon
+            precision (Union[float, None], optional): The precision used when preparing the single
+                qubit rotations used to synthesize the n-qubit unitary.
+
+        Returns:
+            CompressedResourceOp: the operator in a compressed representation
+        """
+        params = {"num_wires": num_wires, "precision": precision}
+        return CompressedResourceOp(cls, params)
+
+    @classmethod
+    def default_resource_decomp(cls, num_wires, precision=None, **kwargs) -> list[GateCount]:
+        r"""Returns a list representing the resources of the operator. Each object in the list
+        represents a gate and the number of times it occurs in the circuit.
+
+        Args:
+            num_wires (int): the number of qubits the operation acts upon
+            precision (Union[float, None], optional): The precision used when preparing the single
+                qubit rotations used to synthesize the n-qubit unitary.
+
+        Resources:
+            The resources are defined by combining the two equalities in `Möttönen and Vartiainen
+            (2005), Fig 14 <https://arxiv.org/pdf/quant-ph/0504100>`_, we can express an :math:`n`-
+            qubit unitary as four :math:`n - 1`-qubit unitaries and three multiplexed rotations
+            via (:class:`~.labs.resource_estimation.ResourceSelectPauliRot`). Specifically, the cost
+            is given by:
+
+            * 1-qubit unitary, the cost is approximated as a single :code:`RZ` rotation.
+
+            * 2-qubit unitary, the cost is approximated as four single qubit rotations and three :code:`CNOT` gates.
+
+            * 3-qubit unitary or more, the cost is given according to the reference above, recursively.
+
+        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.
+        """
+        gate_lst = []
+        precision = precision or kwargs["config"]["precision_qubit_unitary"]
+
+        one_qubit_decomp_cost = [GateCount(resource_rep(re.ResourceRZ, {"eps": precision}))]
+        two_qubit_decomp_cost = [
+            GateCount(resource_rep(re.ResourceRZ, {"eps": precision}), 4),
+            GateCount(resource_rep(re.ResourceCNOT), 3),
+        ]
+
+        if num_wires == 1:
+            return one_qubit_decomp_cost
+
+        if num_wires == 2:
+            return two_qubit_decomp_cost
+
+        for gc in two_qubit_decomp_cost:
+            gate_lst.append(4 ** (num_wires - 2) * gc)
+
+        for index in range(2, num_wires):
+            multiplex_z = resource_rep(
+                ResourceSelectPauliRot,
+                {
+                    "num_ctrl_wires": index,
+                    "rotation_axis": "Z",
+                    "precision": precision,
+                },
+            )
+            multiplex_y = resource_rep(
+                ResourceSelectPauliRot,
+                {
+                    "num_ctrl_wires": index,
+                    "rotation_axis": "Y",
+                    "precision": precision,
+                },
+            )
+
+            gate_lst.append(GateCount(multiplex_z, 2 * 4 ** (num_wires - (1 + index))))
+            gate_lst.append(GateCount(multiplex_y, 4 ** (num_wires - (1 + index))))
+
+        return gate_lst
+
+
+class ResourceSelectPauliRot(ResourceOperator):
+    r"""Resource class for the SelectPauliRot gate.
+
+    Args:
+        rotation_axis (str): the rotation axis used in the multiplexer
+        num_ctrl_wires (int): the number of control wires of the multiplexer
+        precision (float): the precision used in the single qubit rotations
+        wires (Sequence[int], optional): the wires the operation acts on
+
+    Resources:
+        The resources are obtained from the construction scheme given in `Möttönen and Vartiainen
+        (2005), Fig 7a <https://arxiv.org/abs/quant-ph/0504100>`_. Specifically, the resources
+        for an :math:`n` qubit unitary are given as :math:`2^{n}` instances of the :code:`CNOT`
+        gate and :math:`2^{n}` instances of the single qubit rotation gate (:code:`RX`,
+        :code:`RY` or :code:`RZ`) depending on the :code:`rotation_axis`.
+
+    .. seealso:: :class:`~.SelectPauliRot`
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> mltplxr = plre.ResourceSelectPauliRot(
+    ...     rotation_axis = "Y",
+    ...     num_ctrl_wires = 4,
+    ...     precision = 1e-3,
+    ... )
+    >>> print(plre.estimate_resources(mltplxr, plre.StandardGateSet))
+    --- Resources: ---
+     Total qubits: 5
+     Total gates : 32
+     Qubit breakdown:
+      clean qubits: 0, dirty qubits: 0, algorithmic qubits: 5
+     Gate breakdown:
+      {'RY': 16, 'CNOT': 16}
+    """
+
+    resource_keys = {"num_ctrl_wires", "rotation_axis", "precision"}
+
+    def __init__(self, rotation_axis: str, num_ctrl_wires: int, precision=None, wires=None) -> None:
+        if rotation_axis not in ("X", "Y", "Z"):
+            raise ValueError("The `rotation_axis` argument must be one of ('X', 'Y', 'Z')")
+
+        self.num_ctrl_wires = num_ctrl_wires
+        self.rotation_axis = rotation_axis
+        self.precision = precision
+
+        self.num_wires = num_ctrl_wires + 1
+        super().__init__(wires=wires)
+
+    @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:
+            * rotation_axis (str): the rotation axis used in the multiplexer
+            * num_ctrl_wires (int): the number of control wires of the multiplexer
+            * precision (float): the precision used in the single qubit rotations
+        """
+        return {
+            "num_ctrl_wires": self.num_ctrl_wires,
+            "rotation_axis": self.rotation_axis,
+            "precision": self.precision,
+        }
+
+    @classmethod
+    def resource_rep(cls, num_ctrl_wires, rotation_axis, precision=None):
+        r"""Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute the resources.
+
+        Args:
+            rotation_axis (str): the rotation axis used in the multiplexer
+            num_ctrl_wires (int): the number of control wires of the multiplexer
+            precision (float): the precision used in the single qubit rotations
+
+        Returns:
+            CompressedResourceOp: the operator in a compressed representation
+        """
+        return CompressedResourceOp(
+            cls,
+            {
+                "num_ctrl_wires": num_ctrl_wires,
+                "rotation_axis": rotation_axis,
+                "precision": precision,
+            },
+        )
+
+    @classmethod
+    def default_resource_decomp(cls, num_ctrl_wires, rotation_axis, precision, **kwargs):
+        r"""Returns a list representing the resources of the operator. Each object in the list
+        represents a gate and the number of times it occurs in the circuit.
+
+        Args:
+            rotation_axis (str): the rotation axis used in the multiplexer
+            num_ctrl_wires (int): the number of control wires of the multiplexer
+            precision (float): the precision used in the single qubit rotations
+            wires (Sequence[int], optional): the wires the operation acts on
+
+        Resources:
+            The resources are obtained from the construction scheme given in `Möttönen and Vartiainen
+            (2005), Fig 7a <https://arxiv.org/abs/quant-ph/0504100>`_. Specifically, the resources
+            for an :math:`n` qubit unitary are given as :math:`2^{n}` instances of the :code:`CNOT`
+            gate and :math:`2^{n}` instances of the single qubit rotation gate (:code:`RX`,
+            :code:`RY` or :code:`RZ`) depending on the :code:`rotation_axis`.
+
+        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.
+        """
+        rotation_gate_map = {
+            "X": re.ResourceRX,
+            "Y": re.ResourceRY,
+            "Z": re.ResourceRZ,
+        }
+        precision = precision or kwargs["config"]["precision_select_pauli_rot"]
+
+        gate = resource_rep(rotation_gate_map[rotation_axis], {"eps": precision})
+        cnot = resource_rep(re.ResourceCNOT)
+
+        gate_lst = [
+            GateCount(gate, 2**num_ctrl_wires),
+            GateCount(cnot, 2**num_ctrl_wires),
+        ]
+
+        return gate_lst
+
+    @classmethod
+    def phase_grad_resource_decomp(cls, num_ctrl_wires, rotation_axis, precision, **kwargs):
+        r"""Returns a list representing the resources of the operator. Each object in the list
+        represents a gate and the number of times it occurs in the circuit.
+
+        Args:
+            rotation_axis (str): the rotation axis used in the multiplexer
+            num_ctrl_wires (int): the number of control wires of the multiplexer
+            precision (float): the precision used in the single qubit rotations
+            wires (Sequence[int], optional): the wires the operation acts on
+
+        Resources:
+            The resources are obtained from the construction scheme given in `O'Brien and Sünderhauf
+            (2025), Fig 4 <https://arxiv.org/pdf/2409.07332>`_. Specifically, the resources
+            use two :code:`~.labs.resource_estimation.ResourceQROM`s to digitally load and unload
+            the phase angles up to some precision. These are then applied using a single controlled
+            :code:`~.labs.resource_estimation.ResourceSemiAdder`.
+
+            .. note::
+
+                This method assumes a phase gradient state is prepared on an auxiliary register.
+
+        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.
+        """
+        precision = precision or kwargs["config"]["precision_select_pauli_rot"]
+        num_prec_wires = abs(math.floor(math.log2(precision)))
+        gate_lst = []
+
+        qrom = resource_rep(
+            re.ResourceQROM,
+            {
+                "num_bitstrings": 2**num_ctrl_wires,
+                "num_bit_flips": 2**num_ctrl_wires * num_prec_wires / 2,
+                "size_bitstring": num_prec_wires,
+                "clean": False,
+            },
+        )
+
+        gate_lst.append(AllocWires(num_prec_wires))
+        gate_lst.append(GateCount(qrom))
+        gate_lst.append(
+            GateCount(
+                resource_rep(
+                    re.ResourceControlled,
+                    {
+                        "base_cmpr_op": resource_rep(
+                            re.ResourceSemiAdder,
+                            {"max_register_size": num_prec_wires},
+                        ),
+                        "num_ctrl_wires": 1,
+                        "num_ctrl_values": 0,
+                    },
+                )
+            )
+        )
+        gate_lst.append(GateCount(resource_rep(re.ResourceAdjoint, {"base_cmpr_op": qrom})))
+        gate_lst.append(FreeWires(num_prec_wires))
+
+        h = resource_rep(re.ResourceHadamard)
+        s = resource_rep(re.ResourceS)
+        s_dagg = resource_rep(re.ResourceAdjoint, {"base_cmpr_op": s})
+
+        if rotation_axis == "X":
+            gate_lst.append(GateCount(h, 2))
+        if rotation_axis == "Y":
+            gate_lst.append(GateCount(h, 2))
+            gate_lst.append(GateCount(s))
+            gate_lst.append(GateCount(s_dagg))
+
+        return gate_lst


Test patch:
diff --git a/pennylane/labs/tests/resource_estimation/ops/op_math/test_controlled_ops.py b/pennylane/labs/tests/resource_estimation/ops/op_math/test_controlled_ops.py
index 6d47f83c5..ce64fcc85 100644
--- a/pennylane/labs/tests/resource_estimation/ops/op_math/test_controlled_ops.py
+++ b/pennylane/labs/tests/resource_estimation/ops/op_math/test_controlled_ops.py
@@ -655,6 +655,10 @@ class TestResourceCRX:
 
     op = re.ResourceCRX(wires=[0, 1])
 
+    def test_resource_keys(self):
+        """test that the resource keys are correct"""
+        assert self.op.resource_keys == {"eps"}
+
     def test_resources(self):
         """Test that the resources method produces the expected resources."""
 
@@ -724,6 +728,10 @@ class TestResourceCRY:
 
     op = re.ResourceCRY(wires=[0, 1])
 
+    def test_resource_keys(self):
+        """test that the resource keys are correct"""
+        assert self.op.resource_keys == {"eps"}
+
     def test_resources(self):
         """Test that the resources method produces the expected resources."""
 
@@ -791,6 +799,10 @@ class TestResourceCRZ:
 
     op = re.ResourceCRZ(wires=[0, 1])
 
+    def test_resource_keys(self):
+        """test that the resource keys are correct"""
+        assert self.op.resource_keys == {"eps"}
+
     def test_resources(self):
         """Test that the resources method produces the expected resources."""
 
@@ -858,6 +870,10 @@ class TestResourceCRot:
 
     op = re.ResourceCRot(wires=[0, 1])
 
+    def test_resource_keys(self):
+        """test that the resource keys are correct"""
+        assert self.op.resource_keys == {"eps"}
+
     def test_resources(self):
         """Test that the resources method produces the expected resources."""
         expected_resources = [
@@ -923,6 +939,11 @@ class TestResourceCRot:
 class TestResourceControlledPhaseShift:
     """Test ResourceControlledPhaseShift"""
 
+    def test_resource_keys(self):
+        """test that the resource keys are correct"""
+        op = re.ResourceControlledPhaseShift()
+        assert op.resource_keys == {"eps"}
+
     def test_resources(self):
         """Test the resources method"""
 
diff --git a/pennylane/labs/tests/resource_estimation/templates/test_subroutines.py b/pennylane/labs/tests/resource_estimation/templates/test_subroutines.py
index e4c1c30fe..199a5db99 100644
--- a/pennylane/labs/tests/resource_estimation/templates/test_subroutines.py
+++ b/pennylane/labs/tests/resource_estimation/templates/test_subroutines.py
@@ -17,6 +17,7 @@ Tests for quantum algorithmic subroutines resource operators.
 import pytest
 
 import pennylane.labs.resource_estimation as plre
+from pennylane.labs.resource_estimation import AllocWires, FreeWires, GateCount, resource_rep
 
 # pylint: disable=no-self-use,too-many-arguments
 
@@ -42,8 +43,8 @@ class TestResourceOutOfPlaceSquare:
     def test_resources(self, register_size):
         """Test that the resources are correct."""
         expected = [
-            plre.GateCount(plre.resource_rep(plre.ResourceToffoli), (register_size - 1) ** 2),
-            plre.GateCount(plre.resource_rep(plre.ResourceCNOT), register_size),
+            GateCount(resource_rep(plre.ResourceToffoli), (register_size - 1) ** 2),
+            GateCount(resource_rep(plre.ResourceCNOT), register_size),
         ]
         assert (
             plre.ResourceOutOfPlaceSquare.resource_decomp(register_size=register_size) == expected
@@ -71,35 +72,35 @@ class TestResourcePhaseGradient:
             (
                 1,
                 [
-                    plre.GateCount(plre.ResourceHadamard.resource_rep()),
-                    plre.GateCount(plre.ResourceZ.resource_rep()),
+                    GateCount(plre.ResourceHadamard.resource_rep()),
+                    GateCount(plre.ResourceZ.resource_rep()),
                 ],
             ),
             (
                 2,
                 [
-                    plre.GateCount(plre.ResourceHadamard.resource_rep(), 2),
-                    plre.GateCount(plre.ResourceZ.resource_rep()),
-                    plre.GateCount(plre.ResourceS.resource_rep()),
+                    GateCount(plre.ResourceHadamard.resource_rep(), 2),
+                    GateCount(plre.ResourceZ.resource_rep()),
+                    GateCount(plre.ResourceS.resource_rep()),
                 ],
             ),
             (
                 3,
                 [
-                    plre.GateCount(plre.ResourceHadamard.resource_rep(), 3),
-                    plre.GateCount(plre.ResourceZ.resource_rep()),
-                    plre.GateCount(plre.ResourceS.resource_rep()),
-                    plre.GateCount(plre.ResourceT.resource_rep()),
+                    GateCount(plre.ResourceHadamard.resource_rep(), 3),
+                    GateCount(plre.ResourceZ.resource_rep()),
+                    GateCount(plre.ResourceS.resource_rep()),
+                    GateCount(plre.ResourceT.resource_rep()),
                 ],
             ),
             (
                 5,
                 [
-                    plre.GateCount(plre.ResourceHadamard.resource_rep(), 5),
-                    plre.GateCount(plre.ResourceZ.resource_rep()),
-                    plre.GateCount(plre.ResourceS.resource_rep()),
-                    plre.GateCount(plre.ResourceT.resource_rep()),
-                    plre.GateCount(plre.ResourceRZ.resource_rep(), 2),
+                    GateCount(plre.ResourceHadamard.resource_rep(), 5),
+                    GateCount(plre.ResourceZ.resource_rep()),
+                    GateCount(plre.ResourceS.resource_rep()),
+                    GateCount(plre.ResourceT.resource_rep()),
+                    GateCount(plre.ResourceRZ.resource_rep(), 2),
                 ],
             ),
         ),
@@ -137,17 +138,17 @@ class TestResourceOutMultiplier:
         a_register_size = 5
         b_register_size = 3
 
-        toff = plre.resource_rep(plre.ResourceToffoli)
-        l_elbow = plre.resource_rep(plre.ResourceTempAND)
-        r_elbow = plre.resource_rep(plre.ResourceAdjoint, {"base_cmpr_op": l_elbow})
+        toff = resource_rep(plre.ResourceToffoli)
+        l_elbow = resource_rep(plre.ResourceTempAND)
+        r_elbow = resource_rep(plre.ResourceAdjoint, {"base_cmpr_op": l_elbow})
 
         num_elbows = 12
         num_toff = 1
 
         expected = [
-            plre.GateCount(l_elbow, num_elbows),
-            plre.GateCount(r_elbow, num_elbows),
-            plre.GateCount(toff, num_toff),
+            GateCount(l_elbow, num_elbows),
+            GateCount(r_elbow, num_elbows),
+            GateCount(toff, num_toff),
         ]
         assert (
             plre.ResourceOutMultiplier.resource_decomp(a_register_size, b_register_size) == expected
@@ -176,26 +177,26 @@ class TestResourceSemiAdder:
         (
             (
                 1,
-                [plre.GateCount(plre.resource_rep(plre.ResourceCNOT))],
+                [GateCount(resource_rep(plre.ResourceCNOT))],
             ),
             (
                 2,
                 [
-                    plre.GateCount(plre.resource_rep(plre.ResourceCNOT), 2),
-                    plre.GateCount(plre.resource_rep(plre.ResourceX), 2),
-                    plre.GateCount(plre.resource_rep(plre.ResourceToffoli)),
+                    GateCount(resource_rep(plre.ResourceCNOT), 2),
+                    GateCount(resource_rep(plre.ResourceX), 2),
+                    GateCount(resource_rep(plre.ResourceToffoli)),
                 ],
             ),
             (
                 3,
                 [
                     plre.AllocWires(2),
-                    plre.GateCount(plre.resource_rep(plre.ResourceCNOT), 9),
-                    plre.GateCount(plre.resource_rep(plre.ResourceTempAND), 2),
-                    plre.GateCount(
-                        plre.resource_rep(
+                    GateCount(resource_rep(plre.ResourceCNOT), 9),
+                    GateCount(resource_rep(plre.ResourceTempAND), 2),
+                    GateCount(
+                        resource_rep(
                             plre.ResourceAdjoint,
-                            {"base_cmpr_op": plre.resource_rep(plre.ResourceTempAND)},
+                            {"base_cmpr_op": resource_rep(plre.ResourceTempAND)},
                         ),
                         2,
                     ),
@@ -218,11 +219,11 @@ class TestResourceSemiAdder:
 
         expected_res = [
             plre.AllocWires(4),
-            plre.GateCount(plre.resource_rep(plre.ResourceCNOT), 24),
-            plre.GateCount(plre.resource_rep(plre.ResourceTempAND), 8),
-            plre.GateCount(
-                plre.resource_rep(
-                    plre.ResourceAdjoint, {"base_cmpr_op": plre.resource_rep(plre.ResourceTempAND)}
+            GateCount(resource_rep(plre.ResourceCNOT), 24),
+            GateCount(resource_rep(plre.ResourceTempAND), 8),
+            GateCount(
+                resource_rep(
+                    plre.ResourceAdjoint, {"base_cmpr_op": resource_rep(plre.ResourceTempAND)}
                 ),
                 8,
             ),
@@ -250,12 +251,8 @@ class TestResourceBasisRotation:
     def test_resources(self, dim_n):
         """Test that the resources are correct."""
         expected = [
-            plre.GateCount(
-                plre.resource_rep(plre.ResourcePhaseShift), dim_n + (dim_n * (dim_n - 1) // 2)
-            ),
-            plre.GateCount(
-                plre.resource_rep(plre.ResourceSingleExcitation), dim_n * (dim_n - 1) // 2
-            ),
+            GateCount(resource_rep(plre.ResourcePhaseShift), dim_n + (dim_n * (dim_n - 1) // 2)),
+            GateCount(resource_rep(plre.ResourceSingleExcitation), dim_n * (dim_n - 1) // 2),
         ]
         assert plre.ResourceBasisRotation.resource_decomp(dim_n) == expected
 
@@ -286,31 +283,31 @@ class TestResourceSelect:
 
         expected = [
             plre.AllocWires(1),
-            plre.GateCount(
+            GateCount(
                 plre.ResourceControlled.resource_rep(
                     plre.ResourceRX.resource_rep(),
                     1,
                     0,
                 )
             ),
-            plre.GateCount(
+            GateCount(
                 plre.ResourceControlled.resource_rep(
                     plre.ResourceZ.resource_rep(),
                     1,
                     0,
                 )
             ),
-            plre.GateCount(
+            GateCount(
                 plre.ResourceControlled.resource_rep(
                     plre.ResourceCNOT.resource_rep(),
                     1,
                     0,
                 )
             ),
-            plre.GateCount(plre.ResourceX.resource_rep(), 4),
-            plre.GateCount(plre.ResourceCNOT.resource_rep(), 2),
-            plre.GateCount(plre.ResourceTempAND.resource_rep(), 2),
-            plre.GateCount(
+            GateCount(plre.ResourceX.resource_rep(), 4),
+            GateCount(plre.ResourceCNOT.resource_rep(), 2),
+            GateCount(plre.ResourceTempAND.resource_rep(), 2),
+            GateCount(
                 plre.ResourceAdjoint.resource_rep(
                     plre.ResourceTempAND.resource_rep(),
                 ),
@@ -389,18 +386,18 @@ class TestResourceQROM:
                 True,
                 [
                     plre.AllocWires(5),
-                    plre.GateCount(plre.ResourceHadamard.resource_rep(), 6),
-                    plre.GateCount(plre.ResourceX.resource_rep(), 14),
-                    plre.GateCount(plre.ResourceCNOT.resource_rep(), 36),
-                    plre.GateCount(plre.ResourceTempAND.resource_rep(), 6),
-                    plre.GateCount(
+                    GateCount(plre.ResourceHadamard.resource_rep(), 6),
+                    GateCount(plre.ResourceX.resource_rep(), 14),
+                    GateCount(plre.ResourceCNOT.resource_rep(), 36),
+                    GateCount(plre.ResourceTempAND.resource_rep(), 6),
+                    GateCount(
                         plre.ResourceAdjoint.resource_rep(
                             plre.ResourceTempAND.resource_rep(),
                         ),
                         6,
                     ),
                     plre.FreeWires(2),
-                    plre.GateCount(plre.ResourceCSWAP.resource_rep(), 12),
+                    GateCount(plre.ResourceCSWAP.resource_rep(), 12),
                     plre.FreeWires(3),
                 ],
             ),
@@ -412,17 +409,17 @@ class TestResourceQROM:
                 False,
                 [
                     plre.AllocWires(10),
-                    plre.GateCount(plre.ResourceX.resource_rep(), 97),
-                    plre.GateCount(plre.ResourceCNOT.resource_rep(), 98),
-                    plre.GateCount(plre.ResourceTempAND.resource_rep(), 48),
-                    plre.GateCount(
+                    GateCount(plre.ResourceX.resource_rep(), 97),
+                    GateCount(plre.ResourceCNOT.resource_rep(), 98),
+                    GateCount(plre.ResourceTempAND.resource_rep(), 48),
+                    GateCount(
                         plre.ResourceAdjoint.resource_rep(
                             plre.ResourceTempAND.resource_rep(),
                         ),
                         48,
                     ),
                     plre.FreeWires(5),
-                    plre.GateCount(plre.ResourceCSWAP.resource_rep(), 5),
+                    GateCount(plre.ResourceCSWAP.resource_rep(), 5),
                 ],
             ),
             (
@@ -433,18 +430,18 @@ class TestResourceQROM:
                 True,  # AllocWires(3), (4 x Hadamard), (42 x X), (30 x CNOT), (20 x TempAND), (20 x Adjoint(TempAND)), FreeWires(3), (0 x CSWAP), FreeWires(0)
                 [
                     plre.AllocWires(3),
-                    plre.GateCount(plre.ResourceHadamard.resource_rep(), 4),
-                    plre.GateCount(plre.ResourceX.resource_rep(), 42),
-                    plre.GateCount(plre.ResourceCNOT.resource_rep(), 30),
-                    plre.GateCount(plre.ResourceTempAND.resource_rep(), 20),
-                    plre.GateCount(
+                    GateCount(plre.ResourceHadamard.resource_rep(), 4),
+                    GateCount(plre.ResourceX.resource_rep(), 42),
+                    GateCount(plre.ResourceCNOT.resource_rep(), 30),
+                    GateCount(plre.ResourceTempAND.resource_rep(), 20),
+                    GateCount(
                         plre.ResourceAdjoint.resource_rep(
                             plre.ResourceTempAND.resource_rep(),
                         ),
                         20,
                     ),
                     plre.FreeWires(3),
-                    plre.GateCount(plre.ResourceCSWAP.resource_rep(), 0),
+                    GateCount(plre.ResourceCSWAP.resource_rep(), 0),
                     plre.FreeWires(0),
                 ],
             ),
@@ -464,3 +461,352 @@ class TestResourceQROM:
             )
             == expected_res
         )
+
+
+class TestResourceQubitUnitary:
+    """Test the ResourceQubitUnitary template"""
+
+    @pytest.mark.parametrize("eps", (None, 1e-3, 1e-5))
+    @pytest.mark.parametrize("num_wires", (1, 2, 3, 4, 5, 6))
+    def test_resource_params(self, num_wires, eps):
+        """Test that the resource params are correct."""
+        op = (
+            plre.ResourceQubitUnitary(num_wires, eps)
+            if eps
+            else plre.ResourceQubitUnitary(num_wires)
+        )
+        assert op.resource_params == {"num_wires": num_wires, "precision": eps}
+
+    @pytest.mark.parametrize("eps", (None, 1e-3, 1e-5))
+    @pytest.mark.parametrize("num_wires", (1, 2, 3, 4, 5, 6))
+    def test_resource_rep(self, num_wires, eps):
+        """Test that the compressed representation is correct."""
+        expected = plre.CompressedResourceOp(
+            plre.ResourceQubitUnitary, {"num_wires": num_wires, "precision": eps}
+        )
+        assert (
+            plre.ResourceQubitUnitary.resource_rep(num_wires=num_wires, precision=eps) == expected
+        )
+
+    @pytest.mark.parametrize(
+        "num_wires, eps, expected_res",
+        (
+            (
+                1,
+                None,
+                [
+                    GateCount(resource_rep(plre.ResourceRZ, {"eps": 1e-9})),
+                ],
+            ),
+            (
+                2,
+                1e-3,
+                [
+                    GateCount(resource_rep(plre.ResourceRZ, {"eps": 1e-3}), 4),
+                    GateCount(resource_rep(plre.ResourceCNOT), 3),
+                ],
+            ),
+            (
+                5,
+                1e-5,
+                [
+                    GateCount(resource_rep(plre.ResourceRZ, {"eps": 1e-5}), (4**3) * 4),
+                    GateCount(resource_rep(plre.ResourceCNOT), (4**3) * 3),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceSelectPauliRot,
+                            {
+                                "rotation_axis": "Z",
+                                "num_ctrl_wires": 2,
+                                "precision": 1e-5,
+                            },
+                        ),
+                        2 * 4**2,
+                    ),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceSelectPauliRot,
+                            {
+                                "rotation_axis": "Y",
+                                "num_ctrl_wires": 2,
+                                "precision": 1e-5,
+                            },
+                        ),
+                        4**2,
+                    ),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceSelectPauliRot,
+                            {
+                                "rotation_axis": "Z",
+                                "num_ctrl_wires": 3,
+                                "precision": 1e-5,
+                            },
+                        ),
+                        2 * 4,
+                    ),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceSelectPauliRot,
+                            {
+                                "rotation_axis": "Y",
+                                "num_ctrl_wires": 3,
+                                "precision": 1e-5,
+                            },
+                        ),
+                        4,
+                    ),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceSelectPauliRot,
+                            {
+                                "rotation_axis": "Z",
+                                "num_ctrl_wires": 4,
+                                "precision": 1e-5,
+                            },
+                        ),
+                        2,
+                    ),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceSelectPauliRot,
+                            {
+                                "rotation_axis": "Y",
+                                "num_ctrl_wires": 4,
+                                "precision": 1e-5,
+                            },
+                        ),
+                        1,
+                    ),
+                ],
+            ),
+        ),
+    )
+    def test_default_resources(self, num_wires, eps, expected_res):
+        """Test that the resources are correct."""
+        if eps is None:
+            config = {"precision_qubit_unitary": 1e-9}
+            assert (
+                plre.ResourceQubitUnitary.resource_decomp(
+                    num_wires=num_wires, precision=eps, config=config
+                )
+                == expected_res
+            )
+        else:
+            assert (
+                plre.ResourceQubitUnitary.resource_decomp(num_wires=num_wires, precision=eps)
+                == expected_res
+            )
+
+
+class TestResourceSelectPauliRot:
+    """Test the ResourceSelectPauliRot template"""
+
+    @pytest.mark.parametrize("precision", (None, 1e-3, 1e-5))
+    @pytest.mark.parametrize("rotation_axis", ("X", "Y", "Z"))
+    @pytest.mark.parametrize("num_ctrl_wires", (1, 2, 3, 4, 5))
+    def test_resource_params(self, num_ctrl_wires, rotation_axis, precision):
+        """Test that the resource params are correct."""
+        op = (
+            plre.ResourceSelectPauliRot(rotation_axis, num_ctrl_wires, precision)
+            if precision
+            else plre.ResourceSelectPauliRot(rotation_axis, num_ctrl_wires)
+        )
+        assert op.resource_params == {
+            "rotation_axis": rotation_axis,
+            "num_ctrl_wires": num_ctrl_wires,
+            "precision": precision,
+        }
+
+    @pytest.mark.parametrize("precision", (None, 1e-3, 1e-5))
+    @pytest.mark.parametrize("rotation_axis", ("X", "Y", "Z"))
+    @pytest.mark.parametrize("num_ctrl_wires", (1, 2, 3, 4, 5))
+    def test_resource_rep(self, num_ctrl_wires, rotation_axis, precision):
+        """Test that the compressed representation is correct."""
+        expected = plre.CompressedResourceOp(
+            plre.ResourceSelectPauliRot,
+            {
+                "rotation_axis": rotation_axis,
+                "num_ctrl_wires": num_ctrl_wires,
+                "precision": precision,
+            },
+        )
+        assert (
+            plre.ResourceSelectPauliRot.resource_rep(num_ctrl_wires, rotation_axis, precision)
+            == expected
+        )
+
+    @pytest.mark.parametrize(
+        "num_ctrl_wires, rotation_axis, precision, expected_res",
+        (
+            (
+                1,
+                "X",
+                None,
+                [
+                    GateCount(resource_rep(plre.ResourceRX, {"eps": 1e-9}), 2),
+                    GateCount(resource_rep(plre.ResourceCNOT), 2),
+                ],
+            ),
+            (
+                2,
+                "Y",
+                1e-3,
+                [
+                    GateCount(resource_rep(plre.ResourceRY, {"eps": 1e-3}), 2**2),
+                    GateCount(resource_rep(plre.ResourceCNOT), 2**2),
+                ],
+            ),
+            (
+                5,
+                "Z",
+                1e-5,
+                [
+                    GateCount(resource_rep(plre.ResourceRZ, {"eps": 1e-5}), 2**5),
+                    GateCount(resource_rep(plre.ResourceCNOT), 2**5),
+                ],
+            ),
+        ),
+    )
+    def test_default_resources(self, num_ctrl_wires, rotation_axis, precision, expected_res):
+        """Test that the resources are correct."""
+        if precision is None:
+            config = {"precision_select_pauli_rot": 1e-9}
+            assert (
+                plre.ResourceSelectPauliRot.resource_decomp(
+                    num_ctrl_wires=num_ctrl_wires,
+                    rotation_axis=rotation_axis,
+                    precision=precision,
+                    config=config,
+                )
+                == expected_res
+            )
+        else:
+            assert (
+                plre.ResourceSelectPauliRot.resource_decomp(
+                    num_ctrl_wires=num_ctrl_wires,
+                    rotation_axis=rotation_axis,
+                    precision=precision,
+                )
+                == expected_res
+            )
+
+    @pytest.mark.parametrize(
+        "num_ctrl_wires, rotation_axis, precision, expected_res",
+        (
+            (
+                1,
+                "X",
+                None,
+                [
+                    AllocWires(30),
+                    GateCount(plre.ResourceQROM.resource_rep(2, 30, 30, False)),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceControlled,
+                            {
+                                "base_cmpr_op": plre.ResourceSemiAdder.resource_rep(30),
+                                "num_ctrl_wires": 1,
+                                "num_ctrl_values": 0,
+                            },
+                        )
+                    ),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceAdjoint,
+                            {
+                                "base_cmpr_op": plre.ResourceQROM.resource_rep(2, 30, 30, False),
+                            },
+                        )
+                    ),
+                    FreeWires(30),
+                    GateCount(resource_rep(plre.ResourceHadamard), 2),
+                ],
+            ),
+            (
+                2,
+                "Y",
+                1e-3,
+                [
+                    AllocWires(10),
+                    GateCount(plre.ResourceQROM.resource_rep(4, 10, 20, False)),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceControlled,
+                            {
+                                "base_cmpr_op": plre.ResourceSemiAdder.resource_rep(10),
+                                "num_ctrl_wires": 1,
+                                "num_ctrl_values": 0,
+                            },
+                        )
+                    ),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceAdjoint,
+                            {
+                                "base_cmpr_op": plre.ResourceQROM.resource_rep(4, 10, 20, False),
+                            },
+                        )
+                    ),
+                    FreeWires(10),
+                    GateCount(resource_rep(plre.ResourceHadamard), 2),
+                    GateCount(resource_rep(plre.ResourceS)),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceAdjoint, {"base_cmpr_op": resource_rep(plre.ResourceS)}
+                        )
+                    ),
+                ],
+            ),
+            (
+                5,
+                "Z",
+                1e-5,
+                [
+                    AllocWires(17),
+                    GateCount(plre.ResourceQROM.resource_rep(32, 17, 272, False)),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceControlled,
+                            {
+                                "base_cmpr_op": plre.ResourceSemiAdder.resource_rep(17),
+                                "num_ctrl_wires": 1,
+                                "num_ctrl_values": 0,
+                            },
+                        )
+                    ),
+                    GateCount(
+                        resource_rep(
+                            plre.ResourceAdjoint,
+                            {
+                                "base_cmpr_op": plre.ResourceQROM.resource_rep(32, 17, 272, False),
+                            },
+                        )
+                    ),
+                    FreeWires(17),
+                ],
+            ),
+        ),
+    )
+    def test_phase_gradient_resources(self, num_ctrl_wires, rotation_axis, precision, expected_res):
+        """Test that the resources are correct."""
+        if precision is None:
+            config = {"precision_select_pauli_rot": 1e-9}
+            assert (
+                plre.ResourceSelectPauliRot.phase_grad_resource_decomp(
+                    num_ctrl_wires=num_ctrl_wires,
+                    rotation_axis=rotation_axis,
+                    precision=precision,
+                    config=config,
+                )
+                == expected_res
+            )
+        else:
+            assert (
+                plre.ResourceSelectPauliRot.phase_grad_resource_decomp(
+                    num_ctrl_wires=num_ctrl_wires,
+                    rotation_axis=rotation_axis,
+                    precision=precision,
+                )
+                == expected_res
+            )