Repository: PennyLaneAI/pennylane
Pull request #7730 (2025-06-20 21:14:13)

Added resource template for qubitization of THC
**Context:**
Added template for qubitization of tensor hypercontracted Hamiltonian

**Description of the Change:**

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**


github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index c52f91d97..c021b70e3 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -809,6 +809,10 @@
   templates which will be used to perform resource estimation for trotterization of CDF and THC Hamiltonians respectively.
   [(#7705)](https://github.com/PennyLaneAI/pennylane/pull/7705)
 
+* Added the `pennylane.labs.ResourceQubitize` template which can be used to perform resource
+  estimation for qubitization of THC Hamiltonian.
+  [(#7730)](https://github.com/PennyLaneAI/pennylane/pull/7730)
+
 * Added `pennylane.labs.resource_estimation.ResourceTrotterVibrational` and `pennylane.labs.resource_estimation.ResourceTrotterVibronic`
   templates which can be used to perform resource estimation for trotterization of vibrational and vibronic Hamiltonians respectively.
   [(#7720)](https://github.com/PennyLaneAI/pennylane/pull/7720)
diff --git a/pennylane/labs/resource_estimation/__init__.py b/pennylane/labs/resource_estimation/__init__.py
index 737851cc5..8a702c6be 100644
--- a/pennylane/labs/resource_estimation/__init__.py
+++ b/pennylane/labs/resource_estimation/__init__.py
@@ -129,6 +129,7 @@ Templates:
     ~ResourceTrotterTHC
     ~ResourceTrotterVibrational
     ~ResourceTrotterVibronic
+    ~ResourceQubitizeTHC
 
 Compact Hamiltonian Class:
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -216,4 +217,5 @@ from .templates import (
     CompactHamiltonian,
     ResourceTrotterVibrational,
     ResourceTrotterVibronic,
+    ResourceQubitizeTHC,
 )
diff --git a/pennylane/labs/resource_estimation/templates/__init__.py b/pennylane/labs/resource_estimation/templates/__init__.py
index d46aa6304..48309074f 100644
--- a/pennylane/labs/resource_estimation/templates/__init__.py
+++ b/pennylane/labs/resource_estimation/templates/__init__.py
@@ -28,4 +28,5 @@ from .trotter import (
     ResourceTrotterVibrational,
     ResourceTrotterVibronic,
 )
+from .qubitize import ResourceQubitizeTHC
 from .compact_hamiltonian import CompactHamiltonian
diff --git a/pennylane/labs/resource_estimation/templates/qubitize.py b/pennylane/labs/resource_estimation/templates/qubitize.py
new file mode 100644
index 000000000..cd6ccf579
--- /dev/null
+++ b/pennylane/labs/resource_estimation/templates/qubitize.py
@@ -0,0 +1,268 @@
+# 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 PennyLane subroutine templates."""
+import math
+
+import numpy as np
+
+from pennylane.labs import resource_estimation as plre
+from pennylane.labs.resource_estimation.qubit_manager import AllocWires, FreeWires
+from pennylane.labs.resource_estimation.resource_operator import (
+    CompressedResourceOp,
+    GateCount,
+    ResourceOperator,
+    resource_rep,
+)
+from pennylane.wires import Wires
+
+# pylint: disable=too-many-arguments, arguments-differ
+
+
+class ResourceQubitizeTHC(ResourceOperator):
+    r"""Resource class for Qubitization of THC Hamiltonian.
+
+    Args:
+        compact_ham (~pennylane.labs.resource_estimation.CompactHamiltonian): a tensor hypercontracted
+            Hamiltonian for which the walk operator is being created
+        coeff_precision (float, optional): precision for loading the coefficients of Hamiltonian
+        rotation_precision (float, optional): precision for loading the rotation angles for basis rotation
+        compare_precision (float, optional): precision for comparing two numbers
+        wires (list[int] or optional): the wires on which the operator acts
+
+    Resources:
+        The resources are calculated based on `arXiv:2011.03494 <https://arxiv.org/abs/2011.03494>`_
+
+    **Example**
+
+    The resources for this operation are computed using:
+
+    >>> compact_ham = plre.CompactHamiltonian.thc(num_orbitals=20, tensor_rank=40)
+    >>> res = plre.estimate_resources(plre.ResourceQubitizeTHC(compact_ham))
+    >>> print(res)
+    --- Resources: ---
+     Total qubits: 147.0
+     Total gates : 1.631E+5
+     Qubit breakdown:
+      clean qubits: 10, dirty qubits: 82.0, algorithmic qubits: 55
+     Gate breakdown:
+      {'X': 2.046E+4, 'CNOT': 8.721E+4, 'Toffoli': 1.584E+4, 'Hadamard': 3.923E+4, 'Z': 160, 'Y': 200}
+
+    """
+
+    def __init__(
+        self,
+        compact_ham,
+        coeff_precision=1e-5,
+        rotation_precision=1e-5,
+        compare_precision=1e-3,
+        wires=None,
+    ):
+        if compact_ham.method_name != "thc":
+            raise TypeError(
+                f"Unsupported Hamiltonian representation for ResourceQubitizeTHC."
+                f"This method works with thc Hamiltonian, {compact_ham.method_name} provided"
+            )
+        self.compact_ham = compact_ham
+        self.coeff_precision = coeff_precision
+        self.rotation_precision = rotation_precision
+        self.compare_precision = compare_precision
+
+        if wires is not None:
+            self.wires = Wires(wires)
+            self.num_wires = len(self.wires)
+        else:
+            num_orb = compact_ham.params["num_orbitals"]
+            tensor_rank = compact_ham.params["tensor_rank"]
+            self.num_wires = num_orb * 2 + 2 * int(np.ceil(math.log2(2 * tensor_rank + 1))) + 1
+            self.wires = None
+        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:
+                * compact_ham (~pennylane.labs.resource_estimation.CompactHamiltonian): a tensor hypercontracted
+                  Hamiltonian for which the walk operator is being created
+                * coeff_precision (float, optional): precision for loading the coefficients of Hamiltonian
+                * rotation_precision (float, optional): precision for loading the rotation angles for basis rotation
+                * compare_precision (float, optional): precision for comparing two numbers
+        """
+        return {
+            "compact_ham": self.compact_ham,
+            "coeff_precision": self.coeff_precision,
+            "rotation_precision": self.rotation_precision,
+            "compare_precision": self.compare_precision,
+        }
+
+    @classmethod
+    def resource_rep(
+        cls, compact_ham, coeff_precision, rotation_precision, compare_precision
+    ) -> CompressedResourceOp:
+        """Returns a compressed representation containing only the parameters of
+        the Operator that are needed to compute a resource estimation.
+
+        Args:
+            compact_ham (~pennylane.labs.resource_estimation.CompactHamiltonian): a tensor hypercontracted
+                Hamiltonian for which the walk operator is being created
+            coeff_precision (float, optional): precision for loading the coefficients of Hamiltonian
+            rotation_precision (float, optional): precision for loading the rotation angles for basis rotation
+            compare_precision (float, optional): precision for comparing two numbers
+
+        Returns:
+            CompressedResourceOp: the operator in a compressed representation
+        """
+        params = {
+            "compact_ham": compact_ham,
+            "coeff_precision": coeff_precision,
+            "rotation_precision": rotation_precision,
+            "compare_precision": compare_precision,
+        }
+        return CompressedResourceOp(cls, params)
+
+    @classmethod
+    def default_resource_decomp(
+        cls,
+        compact_ham,
+        coeff_precision=1e-5,
+        rotation_precision=1e-5,
+        compare_precision=1e-3,
+        **kwargs,
+    ) -> list[GateCount]:
+        r"""Returns a list representing the resources of the operator. Each object represents a quantum gate
+        and the number of times it occurs in the decomposition.
+
+        Args:
+            compact_ham (~pennylane.labs.resource_estimation.CompactHamiltonian): a tensor hypercontracted
+                Hamiltonian for which the walk operator is being created
+            coeff_precision (float, optional): precision for loading the coefficients of Hamiltonian
+            rotation_precision (float, optional): precision for loading the rotation angles for basis rotation
+            compare_precision (float, optional): precision for comparing two numbers
+
+        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_orb = compact_ham.params["num_orbitals"]
+        tensor_rank = compact_ham.params["tensor_rank"]
+
+        rot_prec_wires = abs(np.floor(math.log2(rotation_precision)))
+        coeff_wires = abs(int(np.floor(math.log2(coeff_precision))))
+        compare_precision = abs(int(np.floor(math.log2(compare_precision))))
+
+        # Number of qubits needed for the integrals tensors
+        m_register = int(np.ceil(math.log2(2 * tensor_rank + 1)))
+
+        gate_list = []
+
+        # Select Circuit Fig. 5 in arXiv:2011.03494
+        # 1 register of rotation precision for loading the angles,
+        # 2 registers for resource state, 2 wires for spin registers
+
+        gate_list.append(AllocWires(3 * rot_prec_wires + 4))
+
+        # Prepare resource state
+        phasegrad = resource_rep(plre.ResourcePhaseGradient, {"num_wires": rot_prec_wires})
+        gate_list.append(plre.GateCount(phasegrad, 1))
+
+        # Loading the angles controlled on tensor indices
+        qrom_angle = resource_rep(
+            plre.ResourceQROM,
+            {
+                "num_bitstrings": 2**m_register,
+                "size_bitstring": rot_prec_wires,
+                "clean": False,
+                "select_swap_depth": 1,
+            },
+        )
+        gate_list.append(plre.GateCount(qrom_angle, 2 * (num_orb)))
+
+        # Cliffords for conversion to RZ rotations
+        # cost per rotation times 4 for the number of rotations per angle
+
+        h = resource_rep(plre.ResourceHadamard)
+        gate_list.append(plre.GateCount(h, 16 * (num_orb)))
+        z = resource_rep(plre.ResourceZ)
+        gate_list.append(plre.GateCount(z, 8 * (num_orb)))
+        cnot = resource_rep(plre.ResourceCNOT)
+        gate_list.append(plre.GateCount(cnot, 16 * (num_orb)))
+        x = resource_rep(plre.ResourceX)
+        gate_list.append(plre.GateCount(x, 8 * (num_orb)))
+        y = resource_rep(plre.ResourceY)
+        gate_list.append(plre.GateCount(y, 8 * (num_orb)))
+
+        # Swap gates for swapping the state register based on the state
+        cswap = resource_rep(plre.ResourceCSWAP)
+        gate_list.append(plre.GateCount(cswap, 4 * num_orb * (num_orb)))
+
+        # Phase gradient technique for rotation
+        semiadder = resource_rep(
+            plre.ResourceControlled,
+            {
+                "base_cmpr_op": resource_rep(
+                    plre.ResourceSemiAdder,
+                    {"max_register_size": rot_prec_wires},
+                ),
+                "num_ctrl_wires": 1,
+                "num_ctrl_values": 0,
+            },
+        )
+
+        # 2 semiadders required per R gate, and there are 4 R gates per angle.
+        gate_list.append(plre.GateCount(semiadder, 8 * (num_orb)))
+
+        # iZ gate in the center of rotations
+        gate_list.append(plre.GateCount(x, 2 * num_orb))
+        gate_list.append(plre.GateCount(y, 2 * num_orb))
+
+        # unloading the angles
+        gate_list.append(plre.GateCount(qrom_angle, 2 * (num_orb)))
+
+        # Free spin and rotation precision wires
+        gate_list.append(FreeWires(rot_prec_wires + 2))
+
+        # Prepare and unprepare Circuit
+        # This is calculating only the Toffolis from Eq. 33 of arXiv:2011.03494
+        d = num_orb + tensor_rank * (2 * tensor_rank + 1)
+        nd = int(np.ceil(math.log2(num_orb + tensor_rank * (2 * tensor_rank + 1))))
+        gate_list.append(AllocWires(2 * m_register + nd + 2 * coeff_wires))
+
+        m = 2 * m_register + 2 + coeff_wires
+        toffoli = resource_rep(plre.ResourceToffoli)
+        num_toffoli = (
+            28 * m_register
+            + 4 * compare_precision
+            - 18
+            + 2 * m_register**2
+            + 2 * coeff_wires
+            + np.ceil(d / 16)
+            + m * (16 - 1)
+            + np.ceil(d / 16)
+            + 16
+        )
+
+        gate_list.append(plre.GateCount(toffoli, num_toffoli))
+
+        # Reflection
+        mcx = resource_rep(
+            plre.ResourceMultiControlledX, {"num_ctrl_wires": nd, "num_ctrl_values": nd}
+        )
+        gate_list.append(plre.GateCount(h, 2))
+        gate_list.append(plre.GateCount(mcx))
+
+        return gate_list


Test patch:
diff --git a/pennylane/labs/tests/resource_estimation/templates/test_qubitize.py b/pennylane/labs/tests/resource_estimation/templates/test_qubitize.py
new file mode 100644
index 000000000..bcb7e3fab
--- /dev/null
+++ b/pennylane/labs/tests/resource_estimation/templates/test_qubitize.py
@@ -0,0 +1,53 @@
+# 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.
+"""
+Test the Resource classes for Qubitization
+"""
+import pytest
+
+import pennylane.labs.resource_estimation as plre
+
+# pylint: disable=too-many-arguments, no-self-use
+
+
+class TestQubitizeTHC:
+    """Class for testing ResourceTestQubitize"""
+
+    # Expected resources were obtained from Pablo's code
+    hamiltonian_data = [(76, 145, 285, 202695)]
+
+    @pytest.mark.parametrize("num_orbitals, tensor_rank, qubits, toffoli_count", hamiltonian_data)
+    def test_resource_trotter_thc(self, num_orbitals, tensor_rank, qubits, toffoli_count):
+        """Test the ResourceTrotterTHC class for tensor hypercontraction"""
+        compact_ham = plre.CompactHamiltonian.thc(
+            num_orbitals=num_orbitals, tensor_rank=tensor_rank
+        )
+
+        def circ():
+            plre.ResourceQubitizeTHC(
+                compact_ham, coeff_precision=2e-5, rotation_precision=2e-5, compare_precision=1e-2
+            )
+
+        res = plre.estimate_resources(circ)()
+
+        assert res.qubit_manager.total_qubits == qubits
+        assert res.clean_gate_counts["Toffoli"] == toffoli_count
+
+    def test_type_error(self):
+        r"""Test that a TypeError is raised for unsupported Hamiltonian representations."""
+        compact_ham = plre.CompactHamiltonian.cdf(num_orbitals=4, num_fragments=4)
+        with pytest.raises(
+            TypeError, match="Unsupported Hamiltonian representation for ResourceQubitizeTHC"
+        ):
+            plre.ResourceQubitizeTHC(compact_ham)