Repository: PennyLaneAI/pennylane
Pull request #7512 (2025-05-28 22:07:56)

Add single qubit operators
**Context:**
Add single qubit ResourceOperators

**Description of the Change:**

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**


[sc-91013]

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.

Patch:
diff --git a/pennylane/labs/resource_estimation/__init__.py b/pennylane/labs/resource_estimation/__init__.py
index f2f564b4a..a33fe2fb3 100644
--- a/pennylane/labs/resource_estimation/__init__.py
+++ b/pennylane/labs/resource_estimation/__init__.py
@@ -24,21 +24,10 @@ resource estimation.
 
 """
 
-from .ops import (
-    ResourceGlobalPhase,
-    ResourceHadamard,
-    ResourceIdentity,
-    ResourceS,
-    ResourceT,
-    ResourceX,
-    ResourceY,
-    ResourceZ,
-)
-from .qubit_manager import FreeWires, GrabWires, QubitManager
-from .resource_mapping import map_to_resource_op
+from .qubit_manager import QubitManager, GrabWires, FreeWires
+from .resources_base import Resources
 from .resource_operator import (
     CompressedResourceOp,
-    GateCount,
     ResourceOperator,
     ResourcesNotDefined,
     resource_rep,
@@ -46,6 +35,28 @@ from .resource_operator import (
     set_ctrl_decomp,
     set_decomp,
     set_pow_decomp,
+    GateCount,
+)
+from .resource_mapping import map_to_resource_op
+from .resource_tracking import (
+    StandardGateSet,
+    DefaultGateSet,
+    resource_config,
+    estimate_resources,
+)
+
+from .ops import (
+    ResourceHadamard,
+    ResourceS,
+    ResourceX,
+    ResourceY,
+    ResourceZ,
+    ResourceRX,
+    ResourceRY,
+    ResourceRZ,
+    ResourceT,
+    ResourcePhaseShift,
+    ResourceGlobalPhase,
+    ResourceRot,
+    ResourceIdentity
 )
-from .resource_tracking import DefaultGateSet, StandardGateSet, estimate_resources, resource_config
-from .resources_base import Resources
diff --git a/pennylane/labs/resource_estimation/ops/__init__.py b/pennylane/labs/resource_estimation/ops/__init__.py
index fec35db4d..05dab23b3 100644
--- a/pennylane/labs/resource_estimation/ops/__init__.py
+++ b/pennylane/labs/resource_estimation/ops/__init__.py
@@ -13,5 +13,21 @@
 # limitations under the License.
 r"""This module contains resource operators for PennyLane Operators"""
 
-from .identity import ResourceGlobalPhase, ResourceIdentity
-from .qubit import ResourceHadamard, ResourceS, ResourceT, ResourceX, ResourceY, ResourceZ
+from .identity import (
+    ResourceGlobalPhase,
+    ResourceIdentity,
+)
+
+from .qubit import (
+    ResourceHadamard,
+    ResourcePhaseShift,
+    ResourceRot,
+    ResourceRX,
+    ResourceRY,
+    ResourceRZ,
+    ResourceS,
+    ResourceT,
+    ResourceX,
+    ResourceY,
+    ResourceZ,
+)
diff --git a/pennylane/labs/resource_estimation/ops/qubit/__init__.py b/pennylane/labs/resource_estimation/ops/qubit/__init__.py
index 573c820d1..62827b07e 100644
--- a/pennylane/labs/resource_estimation/ops/qubit/__init__.py
+++ b/pennylane/labs/resource_estimation/ops/qubit/__init__.py
@@ -14,3 +14,4 @@
 r"""This module contains experimental resource estimation functionality."""
 
 from .non_parametric_ops import *
+from .parametric_ops_single_qubit import *
diff --git a/pennylane/labs/resource_estimation/ops/qubit/parametric_ops_single_qubit.py b/pennylane/labs/resource_estimation/ops/qubit/parametric_ops_single_qubit.py
new file mode 100644
index 000000000..f1e6716a3
--- /dev/null
+++ b/pennylane/labs/resource_estimation/ops/qubit/parametric_ops_single_qubit.py
@@ -0,0 +1,632 @@
+# 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 single qubit operations."""
+
+import numpy as np
+
+import pennylane.labs.resource_estimation as re
+from pennylane.labs.resource_estimation.resource_operator import (
+    CompressedResourceOp,
+    GateCount,
+    ResourceOperator,
+    resource_rep,
+)
+
+# pylint: disable=arguments-differ
+
+
+def _rotation_resources(epsilon=10e-3):
+    r"""An estimate on the number of T gates needed to implement a Pauli rotation.
+
+    The expected T-count is taken from (the 'Simulation Results' section) `Efficient
+    Synthesis of Universal Repeat-Until-Success Circuits <https://arxiv.org/abs/1404.5320>`_.
+    The cost is given as:
+
+        .. math:: T_{count} = \ceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)
+
+    Args:
+        epsilon (float): the acceptable error threshold for the approximation
+
+    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.
+    """
+    num_gates = round(1.149 * np.log2(1 / epsilon) + 9.2)
+    t = resource_rep(re.ResourceT)
+    return [GateCount(t, num_gates)]
+
+
+class ResourcePhaseShift(ResourceOperator):
+    r"""Resource class for the PhaseShift gate.
+
+    Keyword Args:
+        wires (Sequence[int] or int): the wire the operation acts on
+
+    Resources:
+        The phase shift gate is equivalent to a Z-rotation upto some global phase,
+        as defined from the following identity:
+
+        .. math:: R_\phi(\phi) = e^{i\phi/2}R_z(\phi) = \begin{bmatrix}
+                    1 & 0 \\
+                    0 & e^{i\phi}
+                \end{bmatrix}.
+
+    .. seealso:: :class:`~.PhaseShift`
+
+    **Example**
+
+    The resources for this operation are computed as:
+
+    >>> re.ResourcePhaseShift.resources()
+    {RZ: 1, GlobalPhase: 1}
+    """
+
+    num_wires = 1
+    resource_keys = {"eps"}
+
+    def __init__(self, epsilon=None, wires=None) -> None:
+        self.eps = epsilon
+        super().__init__(wires=wires)
+
+    @property
+    def resource_params(self) -> dict:
+        r"""Returns a dictionary containing the minimal information needed to compute the resources.
+
+        Returns:
+            A dictionary containing the resource parameters:
+                * eps (Union[float, None]): error threshold for the approximation
+        """
+        return {"eps": self.eps} if self.eps else {}
+
+    @classmethod
+    def resource_rep(cls, eps=None) -> CompressedResourceOp:
+        r"""Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation."""
+
+        params = {"eps": eps} if eps else {}
+        return CompressedResourceOp(cls, params)
+
+    @classmethod
+    def default_resource_decomp(cls, eps=None, **kwargs) -> list[GateCount]:
+        r"""Returns a list representing the resources of the operator. The
+        keys are the operators and the associated values are the counts.
+
+        Resources:
+            The phase shift gate is equivalent to a Z-rotation upto some global phase,
+            as defined from the following identity:
+
+            .. math:: R_\phi(\phi) = e^{i\phi/2}R_z(\phi) = \begin{bmatrix}
+                        1 & 0 \\
+                        0 & e^{i\phi}
+                    \end{bmatrix}.
+        """
+        rz = resource_rep(ResourceRZ, {"eps": eps})
+        global_phase = resource_rep(re.ResourceGlobalPhase)
+        return [GateCount(rz), GateCount(global_phase)]
+
+    @classmethod
+    def default_adjoint_resource_decomp(cls, eps=None) -> list[GateCount]:
+        r"""Returns a list representing the resources for the adjoint of the operator.
+
+        Resources:
+            The adjoint of a phase shift 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(eps=eps))]
+
+    @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
+
+        Resources:
+            Taking arbitrary powers of a phase shift produces a sum of shifts.
+            The resources simplify to just one total phase shift operator.
+
+        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 pow_z == 0:
+            return [GateCount(re.ResourceIdentity.resource_rep(eps=eps), 1)]
+        return [GateCount(cls.resource_rep(eps=eps))]
+
+
+class ResourceRX(ResourceOperator):
+    r"""Resource class for the RX gate.
+
+    Keyword Args:
+        eps (float): error threshold for clifford plus T decomposition of this operation
+        wires (Sequence[int] or int): the wire the operation acts on
+
+    Resources:
+        A single qubit rotation gate can be approximately synthesised from Clifford and T gates. The
+        resources are approximating the gate with a series of T gates. The expected T-count is taken
+        from (the 'Simulation Results' section) `Efficient Synthesis of Universal Repeat-Until-Success
+        Circuits <https://arxiv.org/abs/1404.5320>`_. The cost is given as:
+
+        .. math:: T_{count} = \ceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)
+
+    .. seealso:: :class:`~.RX`
+
+    **Example**
+
+    The resources for this operation are computed as:
+
+    >>> re.estimate_resources(re.ResourceRX)
+    --- Resources: ---
+    Total qubits: 1
+    Total gates : 1
+    Qubit breakdown:
+    clean qubits: 0, dirty qubits: 0, algorithmic qubits: 1
+    Gate breakdown:
+    {'T': 17}
+
+    The operation does not require any parameters directly, however, it will depend on the single
+    qubit error threshold, which can be set using a config dictionary.
+
+    >>> config = {"error_rx": 1e-3}
+    >>> re.estimate_resources(re.ResourceRX, config=config)
+    --- Resources: ---
+    Total qubits: 1
+    Total gates : 1
+    Qubit breakdown:
+    clean qubits: 0, dirty qubits: 0, algorithmic qubits: 1
+    Gate breakdown:
+    {'T': 17}
+    """
+
+    num_wires = 1
+    resource_keys = {"eps"}
+
+    def __init__(self, eps=None, wires=None) -> None:
+        self.eps = eps
+        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:
+                * eps (Union[float, None]): the number of qubits the operation is controlled on
+        """
+        return {"eps": self.eps} if self.eps else {}
+
+    @classmethod
+    def resource_rep(cls, eps=None) -> CompressedResourceOp:
+        r"""Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation."""
+
+        params = {"eps": eps} if eps else {}
+        return CompressedResourceOp(cls, params)
+
+    @classmethod
+    def default_resource_decomp(cls, eps=None, **kwargs) -> list[GateCount]:
+        r"""Returns a dictionary representing the resources of the operator. The
+        keys are the operators and the associated values are the counts.
+
+        Keyword Args:
+            eps (float): the error threshold
+
+        Resources:
+            A single qubit rotation gate can be approximately synthesised from Clifford and T gates. The
+            resources are approximating the gate with a series of T gates. The expected T-count is taken
+            from (the 'Simulation Results' section) `Eﬃcient Synthesis of Universal Repeat-Until-Success
+            Circuits <https://arxiv.org/abs/1404.5320>`_. The cost is given as:
+
+            .. math:: T_{count} = \ceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)
+
+        """
+        eps = eps or kwargs["config"]["error_rx"]
+        return _rotation_resources(epsilon=eps)
+
+    @classmethod
+    def default_adjoint_resource_decomp(cls, eps=None) -> list[GateCount]:
+        r"""Returns a dictionary representing the resources for the adjoint of the operator.
+
+        Resources:
+            The adjoint of a single qubit rotation changes the sign of the rotation angle,
+            thus the resources of the adjoint operation result 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))]
+
+    @classmethod
+    def default_pow_resource_decomp(cls, pow_z, eps=None) -> list[GateCount]:
+        r"""Returns a dictionary representing the resources for an operator raised to a power.
+
+        Args:
+            pow_z (int): the power that the operator is being raised to
+
+        Resources:
+            Taking arbitrary powers of a single qubit rotation produces a sum of rotations.
+            The resources simplify to just one total single qubit 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.
+        """
+        if pow_z == 0:
+            return [GateCount(re.ResourceIdentity.resource_rep(eps=eps), 1)]
+
+        return [GateCount(cls.resource_rep(eps))]
+
+
+class ResourceRY(ResourceOperator):
+    r"""Resource class for the RY gate.
+
+    Keyword Args:
+        eps (float): error threshold for clifford plus T decomposition of this operation
+        wires (Sequence[int] or int): the wire the operation acts on
+
+    Resources:
+        A single qubit rotation gate can be approximately synthesised from Clifford and T gates. The
+        resources are approximating the gate with a series of T gates. The expected T-count is taken
+        from (the 'Simulation Results' section) `Efficient Synthesis of Universal Repeat-Until-Success
+        Circuits <https://arxiv.org/abs/1404.5320>`_. The cost is given as:
+
+        .. math:: T_{count} = \ceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)
+
+    .. seealso:: :class:`~.RY`
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> re.estimate_resources(re.ResourceRY)
+    --- Resources: ---
+    Total qubits: 1
+    Total gates : 1
+    Qubit breakdown:
+    clean qubits: 0, dirty qubits: 0, algorithmic qubits: 1
+    Gate breakdown:
+    {'T': 17}
+
+    The operation does not require any parameters directly, however, it will depend on the single
+    qubit error threshold, which can be set using a config dictionary.
+
+    >>> config = {"error_ry": 1e-3}
+    >>> re.estimate_resources(re.ResourceRY, config=config)
+    --- Resources: ---
+    Total qubits: 1
+    Total gates : 1
+    Qubit breakdown:
+    clean qubits: 0, dirty qubits: 0, algorithmic qubits: 1
+    Gate breakdown:
+    {'T': 17}
+
+    """
+
+    num_wires = 1
+    resource_keys = {"eps"}
+
+    def __init__(self, eps=None, wires=None) -> None:
+        self.eps = eps
+        super().__init__(wires=wires)
+
+    @property
+    def resource_params(self) -> dict:
+        r"""Returns a dictionary containing the minimal information needed to compute the resources.
+
+        Returns:
+            A dictionary containing the resource parameters:
+                * eps (Union[float, None]): error threshold for the approximation
+        """
+        return {"eps": self.eps} if self.eps else {}
+
+    @classmethod
+    def resource_rep(cls, eps=None) -> CompressedResourceOp:
+        r"""Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation."""
+
+        params = {"eps": eps} if eps else {}
+        return CompressedResourceOp(cls, params)
+
+    @classmethod
+    def default_resource_decomp(cls, eps=None, **kwargs) -> list[GateCount]:
+        r"""Returns a dictionary representing the resources of the operator. The
+        keys are the operators and the associated values are the counts.
+
+        Keyword Args:
+            eps (float): the error threshold
+
+        Resources:
+            A single qubit rotation gate can be approximately synthesised from Clifford and T gates. The
+            resources are approximating the gate with a series of T gates. The expected T-count is taken
+            from (the 'Simulation Results' section) `Efficient Synthesis of Universal Repeat-Until-Success
+            Circuits <https://arxiv.org/abs/1404.5320>`_. The cost is given as:
+
+            .. math:: T_{count} = \ceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)
+
+        Args:
+            config (dict): a dictionary containing the error threshold
+        """
+        eps = eps or kwargs["config"]["error_ry"]
+        return _rotation_resources(epsilon=eps)
+
+    @classmethod
+    def default_adjoint_resource_decomp(cls, eps=None) -> list[GateCount]:
+        r"""Returns a dictionary representing the resources for the adjoint of the operator.
+
+        Resources:
+            The adjoint of a single qubit rotation changes the sign of the rotation angle,
+            thus the resources of the adjoint operation result 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))]
+
+    @classmethod
+    def default_pow_resource_decomp(cls, pow_z, eps=None) -> list[GateCount]:
+        r"""Returns a dictionary representing the resources for an operator raised to a power.
+
+        Args:
+            pow_z (int): the power that the operator is being raised to
+
+        Resources:
+            Taking arbitrary powers of a single qubit rotation produces a sum of rotations.
+            The resources simplify to just one total single qubit 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.
+        """
+        if pow_z == 0:
+            return [GateCount(re.ResourceIdentity.resource_rep(eps=eps), 1)]
+
+        return [GateCount(cls.resource_rep(eps))]
+
+
+class ResourceRZ(ResourceOperator):
+    r"""Resource class for the RZ gate.
+
+    Keyword Args:
+        eps (float): error threshold for clifford plus T decomposition of this operation
+        wires (Sequence[int] or int): the wire the operation acts on
+
+    Resources:
+        A single qubit rotation gate can be approximately synthesised from Clifford and T gates. The
+        resources are approximating the gate with a series of T gates. The expected T-count is taken
+        from (the 'Simulation Results' section) `Eﬃcient Synthesis of Universal Repeat-Until-Success
+        Circuits <https://arxiv.org/abs/1404.5320>`_. The cost is given as:
+
+        .. math:: T_{count} = \ceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)
+
+    .. seealso:: :class:`~.RZ`
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> re.estimate_resources(re.ResourceRZ)
+    --- Resources: ---
+    Total qubits: 1
+    Total gates : 1
+    Qubit breakdown:
+    clean qubits: 0, dirty qubits: 0, algorithmic qubits: 1
+    Gate breakdown:
+    {'T': 17}
+
+    The operation does not require any parameters directly, however, it will depend on the single
+    qubit error threshold, which can be set using a config dictionary.
+
+    >>> config = {"error_rz": 1e-3}
+    >>> re.estimate_resources(re.ResourceRZ, config=config)
+    --- Resources: ---
+    Total qubits: 1
+    Total gates : 1
+    Qubit breakdown:
+    clean qubits: 0, dirty qubits: 0, algorithmic qubits: 1
+    Gate breakdown:
+    {'T': 17}
+
+    """
+
+    num_wires = 1
+    resource_keys = {"eps"}
+
+    def __init__(self, eps=None, wires=None) -> None:
+        self.eps = eps
+        super().__init__(wires=wires)
+
+    @property
+    def resource_params(self) -> dict:
+        r"""Returns a dictionary containing the minimal information needed to compute the resources.
+
+        Returns:
+            A dictionary containing the resource parameters:
+                * eps (Union[float, None]): error threshold for the approximation
+        """
+        return {"eps": self.eps} if self.eps else {}
+
+    @classmethod
+    def resource_rep(cls, eps=None) -> CompressedResourceOp:
+        r"""Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation."""
+
+        params = {"eps": eps} if eps else {}
+        return CompressedResourceOp(cls, params)
+
+    @classmethod
+    def default_resource_decomp(cls, eps=None, **kwargs) -> list[GateCount]:
+        r"""Returns a dictionary representing the resources of the operator. The
+        keys are the operators and the associated values are the counts.
+
+        Resources:
+            A single qubit rotation gate can be approximately synthesised from Clifford and T gates. The
+            resources are approximating the gate with a series of T gates. The expected T-count is taken
+            from (the 'Simulation Results' section) `Eﬃcient Synthesis of Universal Repeat-Until-Success
+            Circuits <https://arxiv.org/abs/1404.5320>`_. The cost is given as:
+
+            .. math:: T_{count} = \ceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)
+
+        Args:
+            config (dict): a dictionary containing the error threshold
+        """
+        eps = eps or kwargs["config"]["error_rz"]
+        return _rotation_resources(epsilon=eps)
+
+    @classmethod
+    def default_adjoint_resource_decomp(cls, eps=None) -> list[GateCount]:
+        r"""Returns a dictionary representing the resources for the adjoint of the operator.
+
+        Resources:
+            The adjoint of a single qubit rotation changes the sign of the rotation angle,
+            thus the resources of the adjoint operation result 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))]
+
+    @classmethod
+    def default_pow_resource_decomp(cls, pow_z, eps=None) -> list[GateCount]:
+        r"""Returns a dictionary representing the resources for an operator raised to a power.
+
+        Args:
+            pow_z (int): the power that the operator is being raised to
+
+        Resources:
+            Taking arbitrary powers of a single qubit rotation produces a sum of rotations.
+            The resources simplify to just one total single qubit 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.
+        """
+        if pow_z == 0:
+            return [GateCount(re.ResourceIdentity.resource_rep(eps=eps), 1)]
+
+        return [GateCount(cls.resource_rep(eps))]
+
+
+class ResourceRot(ResourceOperator):
+    r"""Resource class for the Rot-gate.
+
+    Args:
+        wires (Any, Wires): the wire the operation acts on
+
+    Resources:
+        The resources are obtained according to the definition of the :class:`Rot` gate:
+
+        .. math:: \hat{R}(\omega, \theta, \phi) = \hat{RZ}(\omega) \cdot \hat{RY}(\theta) \cdot \hat{RZ}(\phi).
+
+    .. seealso:: :class:`~.Rot`
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> re.ResourceRot.resource_decomp()
+    {RY: 1, RZ: 2}
+    """
+
+    num_wires = 1
+    resource_keys = {"eps"}
+
+    def __init__(self, eps=None, wires=None) -> None:
+        self.eps = eps
+        super().__init__(wires=wires)
+
+    @property
+    def resource_params(self):
+        r"""Returns a dictionary containing the minimal information needed to compute the resources.
+
+        Returns:
+            A dictionary containing the resource parameters:
+                * eps (Union[float, None]): error threshold for the approximation
+        """
+        return {"eps": self.eps} if self.eps else {}
+
+    @classmethod
+    def resource_rep(cls, eps=None) -> CompressedResourceOp:
+        r"""Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation."""
+
+        params = {"eps": eps} if eps else {}
+        return CompressedResourceOp(cls, params)
+
+    @classmethod
+    def default_resource_decomp(cls, eps=None, **kwargs) -> list[GateCount]:
+        r"""Returns a dictionary representing the resources of the operator. The
+        keys are the operators and the associated values are the counts.
+
+        Resources:
+            The resources are obtained according to the definition of the :class:`Rot` gate:
+
+            .. math:: \hat{R}(\omega, \theta, \phi) = \hat{RZ}(\omega) \cdot \hat{RY}(\theta) \cdot \hat{RZ}(\phi).
+
+        """
+        ry = resource_rep(ResourceRY, {"eps": eps})
+        rz = resource_rep(ResourceRZ, {"eps": eps})
+
+        return [GateCount(ry), GateCount(rz, 2)]
+
+    @classmethod
+    def default_adjoint_resource_decomp(cls, eps=None) -> list[GateCount]:
+        r"""Returns a dictionary representing the resources for the adjoint of the operator.
+
+        Resources:
+            The adjoint of a general single qubit rotation changes the sign of the rotation angles,
+            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))]
+
+    @classmethod
+    def default_pow_resource_decomp(cls, pow_z, eps=None) -> list[GateCount]:
+        r"""Returns a dictionary representing the resources for an operator raised to a power.
+
+        Args:
+            pow_z (int): the power that the operator is being raised to
+
+        Resources:
+            Taking arbitrary powers of a general single qubit rotation produces a sum of rotations.
+            The resources simplify to just one total single qubit 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.
+        """
+        if pow_z == 0:
+            return [GateCount(re.ResourceIdentity.resource_rep(eps=eps), 1)]
+
+        return [GateCount(cls.resource_rep(eps))]


Test patch:
diff --git a/pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_single_qubit.py b/pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_single_qubit.py
new file mode 100644
index 000000000..b766bb0f4
--- /dev/null
+++ b/pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_single_qubit.py
@@ -0,0 +1,215 @@
+# 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 single qubit resource operators.
+"""
+import pytest
+
+import pennylane.labs.resource_estimation as re
+from pennylane.labs.resource_estimation.ops.qubit.parametric_ops_single_qubit import (
+    _rotation_resources,
+)
+
+# pylint: disable=no-self-use, use-implicit-booleaness-not-comparison,too-many-arguments
+
+params = list(zip([10e-3, 10e-4, 10e-5], [17, 21, 24]))
+
+
+@pytest.mark.parametrize("epsilon, expected", params)
+def test_rotation_resources(epsilon, expected):
+    """Test the hardcoded resources used for RX, RY, RZ"""
+    gate_types = [re.GateCount(re.CompressedResourceOp(re.ResourceT, {}), expected)]
+
+    assert gate_types == _rotation_resources(epsilon=epsilon)
+
+
+class TestPauliRotation:
+    """Test ResourceRX, ResourceRY, and ResourceRZ"""
+
+    params_classes = [re.ResourceRX, re.ResourceRY, re.ResourceRZ]
+    params_errors = [10e-3, 10e-4, 10e-5]
+
+    @pytest.mark.parametrize("resource_class", params_classes)
+    @pytest.mark.parametrize("epsilon", params_errors)
+    def test_resources(self, resource_class, epsilon):
+        """Test the resources method"""
+
+        label = "error_" + resource_class.__name__.replace("Resource", "").lower()
+        config = {label: epsilon}
+        op = resource_class(wires=0)
+        assert op.resource_decomp(config=config) == _rotation_resources(epsilon=epsilon)
+
+    @pytest.mark.parametrize("resource_class", params_classes)
+    @pytest.mark.parametrize("epsilon", params_errors)
+    def test_resource_rep(self, resource_class, epsilon):  # pylint: disable=unused-argument
+        """Test the compact representation"""
+        op = resource_class(wires=0)
+        expected = re.CompressedResourceOp(resource_class, {})
+        assert op.resource_rep() == expected
+
+    @pytest.mark.parametrize("resource_class", params_classes)
+    @pytest.mark.parametrize("epsilon", params_errors)
+    def test_resources_from_rep(self, resource_class, epsilon):
+        """Test the resources can be obtained from the compact representation"""
+
+        label = "error_" + resource_class.__name__.replace("Resource", "").lower()
+        config = {label: epsilon}
+        op = resource_class(wires=0)
+        expected = _rotation_resources(epsilon=epsilon)
+
+        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, config=config) == expected
+
+    @pytest.mark.parametrize("resource_class", params_classes)
+    @pytest.mark.parametrize("epsilon", params_errors)
+    def test_resource_params(self, resource_class, epsilon):  # pylint: disable=unused-argument
+        """Test that the resource params are correct"""
+        op = resource_class(epsilon, wires=0)
+        assert op.resource_params == {"eps": epsilon}
+
+    @pytest.mark.parametrize("resource_class", params_classes)
+    @pytest.mark.parametrize("epsilon", params_errors)
+    def test_adjoint_decomposition(self, resource_class, epsilon):
+        """Test that the adjoint decompositions are correct."""
+
+        expected = [re.GateCount(resource_class(epsilon).resource_rep(), 1)]
+        assert resource_class(epsilon).adjoint_resource_decomp() == expected
+
+    @pytest.mark.parametrize("resource_class", params_classes)
+    @pytest.mark.parametrize("epsilon", params_errors)
+    @pytest.mark.parametrize("z", list(range(0, 10)))
+    def test_pow_decomposition(self, resource_class, epsilon, z):
+        """Test that the pow decompositions are correct."""
+
+        expected = [
+            (
+                re.GateCount(resource_class(epsilon).resource_rep(), 1)
+                if z
+                else re.GateCount(re.ResourceIdentity.resource_rep(), 1)
+            )
+        ]
+        assert resource_class(epsilon).pow_resource_decomp(z) == expected
+
+
+class TestRot:
+    """Test ResourceRot"""
+
+    def test_resources(self):
+        """Test the resources method"""
+        op = re.ResourceRot(wires=0)
+        ry = re.ResourceRY.resource_rep()
+        rz = re.ResourceRZ.resource_rep()
+        expected = [re.GateCount(ry, 1), re.GateCount(rz, 2)]
+
+        assert op.resource_decomp() == expected
+
+    def test_resource_rep(self):
+        """Test the compressed representation"""
+        op = re.ResourceRot(wires=0)
+        expected = re.CompressedResourceOp(re.ResourceRot, {})
+        assert op.resource_rep() == expected
+
+    def test_resources_from_rep(self):
+        """Test that the resources can be obtained from the compact representation"""
+        op = re.ResourceRot(wires=0)
+        ry = re.ResourceRY.resource_rep()
+        rz = re.ResourceRZ.resource_rep()
+        expected = [re.GateCount(ry, 1), re.GateCount(rz, 2)]
+
+        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
+
+    def test_resource_params(self):
+        """Test that the resource params are correct"""
+        op = re.ResourceRot(wires=0)
+        assert op.resource_params == {}
+
+    def test_adjoint_decomp(self):
+        """Test that the adjoint decomposition is correct"""
+
+        expected = [re.GateCount(re.ResourceRot.resource_rep(), 1)]
+        assert re.ResourceRot.adjoint_resource_decomp() == expected
+
+    pow_data = (
+        (0, [re.GateCount(re.ResourceIdentity.resource_rep(), 1)]),
+        (1, [re.GateCount(re.ResourceRot.resource_rep(), 1)]),
+        (2, [re.GateCount(re.ResourceRot.resource_rep(), 1)]),
+        (5, [re.GateCount(re.ResourceRot.resource_rep(), 1)]),
+    )
+
+    @pytest.mark.parametrize("z, expected_res", pow_data)
+    def test_resource_pow(self, z, expected_res):
+        """Test that the pow resources are as expected"""
+        op = re.ResourceRot()
+        assert op.pow_resource_decomp(z) == expected_res
+
+
+class TestPhaseShift:
+    """Test ResourcePhaseShift"""
+
+    def test_resources(self):
+        """Test the resources method"""
+        op = re.ResourcePhaseShift(0.1, wires=0)
+        rz = re.ResourceRZ.resource_rep()
+        global_phase = re.ResourceGlobalPhase.resource_rep()
+
+        expected = [re.GateCount(rz, 1), re.GateCount(global_phase, 1)]
+
+        assert op.resource_decomp() == expected
+
+    def test_resource_rep(self):
+        """Test the compressed representation"""
+        op = re.ResourcePhaseShift(wires=0)
+        expected = re.CompressedResourceOp(re.ResourcePhaseShift, {})
+        assert op.resource_rep() == expected
+
+    def test_resources_from_rep(self):
+        """Test that the resources can be obtained from the compact representation"""
+        op = re.ResourcePhaseShift(0.1)
+        global_phase = re.ResourceGlobalPhase.resource_rep()
+        rz = re.ResourceRZ.resource_rep(0.1)
+        expected = [re.GateCount(rz, 1), re.GateCount(global_phase, 1)]
+
+        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
+
+    def test_resource_params(self):
+        """Test that the resource params are correct"""
+        op = re.ResourcePhaseShift()
+        assert op.resource_params == {}
+
+    def test_adjoint_decomp(self):
+        """Test that the adjoint decomposition is correct"""
+
+        expected = [re.GateCount(re.ResourcePhaseShift.resource_rep(), 1)]
+        assert re.ResourcePhaseShift.adjoint_resource_decomp() == expected
+
+    pow_data = (
+        (0, [re.GateCount(re.ResourceIdentity.resource_rep(), 1)]),
+        (1, [re.GateCount(re.ResourcePhaseShift.resource_rep(), 1)]),
+        (2, [re.GateCount(re.ResourcePhaseShift.resource_rep(), 1)]),
+        (5, [re.GateCount(re.ResourcePhaseShift.resource_rep(), 1)]),
+    )
+
+    @pytest.mark.parametrize("z, expected_res", pow_data)
+    def test_resource_pow(self, z, expected_res):
+        """Test that the pow resources are as expected"""
+        op = re.ResourcePhaseShift()
+        assert op.pow_resource_decomp(z) == expected_res